@ncukondo/search-hub 0.12.2 → 0.14.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/dist/cli/commands/diff.js +2 -2
- package/dist/cli/commands/diff.js.map +1 -1
- package/dist/cli/commands/query/init.d.ts +5 -0
- package/dist/cli/commands/query/init.d.ts.map +1 -1
- package/dist/cli/commands/query/init.js +9 -1
- package/dist/cli/commands/query/init.js.map +1 -1
- package/dist/cli/commands/query/translate.d.ts.map +1 -1
- package/dist/cli/commands/query/translate.js +5 -0
- package/dist/cli/commands/query/translate.js.map +1 -1
- package/dist/cli/commands/query/validate.d.ts +22 -1
- package/dist/cli/commands/query/validate.d.ts.map +1 -1
- package/dist/cli/commands/query/validate.js +65 -22
- package/dist/cli/commands/query/validate.js.map +1 -1
- package/dist/cli/commands/review/extract.d.ts.map +1 -1
- package/dist/cli/commands/review/extract.js +1 -2
- package/dist/cli/commands/review/extract.js.map +1 -1
- package/dist/cli/commands/review/finalize.d.ts.map +1 -1
- package/dist/cli/commands/review/finalize.js +1 -2
- package/dist/cli/commands/review/finalize.js.map +1 -1
- package/dist/cli/commands/review/init.d.ts.map +1 -1
- package/dist/cli/commands/review/init.js +2 -5
- package/dist/cli/commands/review/init.js.map +1 -1
- package/dist/cli/commands/review/merge.d.ts.map +1 -1
- package/dist/cli/commands/review/merge.js +1 -2
- package/dist/cli/commands/review/merge.js.map +1 -1
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +81 -7
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/suggestions/index.d.ts.map +1 -1
- package/dist/cli/suggestions/index.js +10 -0
- package/dist/cli/suggestions/index.js.map +1 -1
- package/dist/cli/suggestions/rules.d.ts.map +1 -1
- package/dist/cli/suggestions/rules.js +21 -8
- package/dist/cli/suggestions/rules.js.map +1 -1
- package/dist/cli/suggestions/types.d.ts +11 -0
- package/dist/cli/suggestions/types.d.ts.map +1 -1
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -1
- package/dist/providers/arxiv/translator.d.ts.map +1 -1
- package/dist/providers/arxiv/translator.js +5 -2
- package/dist/providers/arxiv/translator.js.map +1 -1
- package/dist/providers/base/types.d.ts +2 -0
- package/dist/providers/base/types.d.ts.map +1 -1
- package/dist/providers/base/types.js.map +1 -1
- package/dist/providers/base/warnings.d.ts +14 -0
- package/dist/providers/base/warnings.d.ts.map +1 -0
- package/dist/providers/base/warnings.js +33 -0
- package/dist/providers/base/warnings.js.map +1 -0
- package/dist/providers/eric/translator.d.ts.map +1 -1
- package/dist/providers/eric/translator.js +5 -2
- package/dist/providers/eric/translator.js.map +1 -1
- package/dist/providers/pubmed/translator.d.ts.map +1 -1
- package/dist/providers/pubmed/translator.js +5 -2
- package/dist/providers/pubmed/translator.js.map +1 -1
- package/dist/providers/scopus/translator.d.ts.map +1 -1
- package/dist/providers/scopus/translator.js +22 -5
- package/dist/providers/scopus/translator.js.map +1 -1
- package/dist/query/__test-helpers__/mock-mesh-client.d.ts +12 -0
- package/dist/query/__test-helpers__/mock-mesh-client.d.ts.map +1 -0
- package/dist/query/index.d.ts +4 -0
- package/dist/query/index.d.ts.map +1 -1
- package/dist/query/json-schema.d.ts +3 -0
- package/dist/query/json-schema.d.ts.map +1 -0
- package/dist/query/json-schema.js +48 -0
- package/dist/query/json-schema.js.map +1 -0
- package/dist/query/mesh-lookup.d.ts +47 -0
- package/dist/query/mesh-lookup.d.ts.map +1 -0
- package/dist/query/mesh-lookup.js +214 -0
- package/dist/query/mesh-lookup.js.map +1 -0
- package/dist/query/parser.js +1 -1
- package/dist/query/parser.js.map +1 -1
- package/dist/query/types.d.ts +2 -2
- package/dist/query/types.d.ts.map +1 -1
- package/dist/query/validator.d.ts +5 -5
- package/dist/query/validator.d.ts.map +1 -1
- package/dist/query/validator.js +5 -2
- package/dist/query/validator.js.map +1 -1
- package/dist/query/vocab-cache.d.ts +15 -0
- package/dist/query/vocab-cache.d.ts.map +1 -0
- package/dist/query/vocab-cache.js +44 -0
- package/dist/query/vocab-cache.js.map +1 -0
- package/dist/query/vocab-validator.d.ts +71 -0
- package/dist/query/vocab-validator.d.ts.map +1 -0
- package/dist/query/vocab-validator.js +153 -0
- package/dist/query/vocab-validator.js.map +1 -0
- package/dist/utils/levenshtein.d.ts +6 -0
- package/dist/utils/levenshtein.d.ts.map +1 -0
- package/dist/utils/levenshtein.js +21 -0
- package/dist/utils/levenshtein.js.map +1 -0
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/cli/suggestions/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAc,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAE/D,YAAY,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAC/D,YAAY,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAoBpE;;;;;;;;;;;GAWG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,gBAAgB,GAAG,IAAI,GAAG,MAAM,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/cli/suggestions/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAc,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAE/D,YAAY,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAC/D,YAAY,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAoBpE;;;;;;;;;;;GAWG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,gBAAgB,GAAG,IAAI,GAAG,MAAM,CA2BxE"}
|
|
@@ -12,8 +12,18 @@ ${lines.join("\n")}`;
|
|
|
12
12
|
function formatSuggestion(result) {
|
|
13
13
|
if (result === null) return "";
|
|
14
14
|
const sections = [];
|
|
15
|
+
if (result.tip) {
|
|
16
|
+
sections.push(result.tip);
|
|
17
|
+
}
|
|
15
18
|
const nextSection = formatSection("Next", result.next);
|
|
16
19
|
if (nextSection) sections.push(nextSection);
|
|
20
|
+
if (result.or && result.or.items.length > 0) {
|
|
21
|
+
const lines = result.or.items.map(
|
|
22
|
+
(s) => s.description ? ` ${s.command} # ${s.description}` : ` ${s.command}`
|
|
23
|
+
);
|
|
24
|
+
sections.push(`${result.or.label}:
|
|
25
|
+
${lines.join("\n")}`);
|
|
26
|
+
}
|
|
17
27
|
const seeAlsoSection = formatSection("See also", result.seeAlso);
|
|
18
28
|
if (seeAlsoSection) sections.push(seeAlsoSection);
|
|
19
29
|
if (sections.length === 0) return "";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":["../../../src/cli/suggestions/index.ts"],"sourcesContent":["import type { Suggestion, SuggestionResult } from './types.js';\n\nexport type { Suggestion, SuggestionResult } from './types.js';\nexport type { SuggestionContext, SuggestionRule } from './types.js';\n\n/**\n * Format a section (Next or See also) with aligned inline comments.\n */\nfunction formatSection(label: string, items: Suggestion[]): string {\n if (items.length === 0) return '';\n\n // Find the longest command to align comments\n const maxCommandLen = Math.max(...items.map((s) => s.command.length));\n const padding = 4; // spaces between command and comment\n\n const lines = items.map((s) => {\n const spaces = ' '.repeat(maxCommandLen - s.command.length + padding);\n return ` ${s.command}${spaces}# ${s.description}`;\n });\n\n return `${label}:\\n${lines.join('\\n')}`;\n}\n\n/**\n * Format suggestion result into a human-readable string.\n *\n * Output format:\n * ```\n * Next:\n * search-hub results <session-id> # 結果を確認\n *\n * See also:\n * search-hub diff <other> <session-id> # 別バージョンと比較\n * ```\n */\nexport function formatSuggestion(result: SuggestionResult | null): string {\n if (result === null) return '';\n\n const sections: string[] = [];\n\n const nextSection = formatSection('Next', result.next);\n if (nextSection) sections.push(nextSection);\n\n const seeAlsoSection = formatSection('See also', result.seeAlso);\n if (seeAlsoSection) sections.push(seeAlsoSection);\n\n if (sections.length === 0) return '';\n\n return '\\n' + sections.join('\\n\\n');\n}\n"],"names":[],"mappings":"AAQA,SAAS,cAAc,OAAe,OAA6B;AACjE,MAAI,MAAM,WAAW,EAAG,QAAO;AAG/B,QAAM,gBAAgB,KAAK,IAAI,GAAG,MAAM,IAAI,CAAC,MAAM,EAAE,QAAQ,MAAM,CAAC;AACpE,QAAM,UAAU;AAEhB,QAAM,QAAQ,MAAM,IAAI,CAAC,MAAM;AAC7B,UAAM,SAAS,IAAI,OAAO,gBAAgB,EAAE,QAAQ,SAAS,OAAO;AACpE,WAAO,KAAK,EAAE,OAAO,GAAG,MAAM,KAAK,EAAE,WAAW;AAAA,EAClD,CAAC;AAED,SAAO,GAAG,KAAK;AAAA,EAAM,MAAM,KAAK,IAAI,CAAC;AACvC;AAcO,SAAS,iBAAiB,QAAyC;AACxE,MAAI,WAAW,KAAM,QAAO;AAE5B,QAAM,WAAqB,CAAA;
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../../../src/cli/suggestions/index.ts"],"sourcesContent":["import type { Suggestion, SuggestionResult } from './types.js';\n\nexport type { Suggestion, SuggestionResult } from './types.js';\nexport type { SuggestionContext, SuggestionRule } from './types.js';\n\n/**\n * Format a section (Next or See also) with aligned inline comments.\n */\nfunction formatSection(label: string, items: Suggestion[]): string {\n if (items.length === 0) return '';\n\n // Find the longest command to align comments\n const maxCommandLen = Math.max(...items.map((s) => s.command.length));\n const padding = 4; // spaces between command and comment\n\n const lines = items.map((s) => {\n const spaces = ' '.repeat(maxCommandLen - s.command.length + padding);\n return ` ${s.command}${spaces}# ${s.description}`;\n });\n\n return `${label}:\\n${lines.join('\\n')}`;\n}\n\n/**\n * Format suggestion result into a human-readable string.\n *\n * Output format:\n * ```\n * Next:\n * search-hub results <session-id> # 結果を確認\n *\n * See also:\n * search-hub diff <other> <session-id> # 別バージョンと比較\n * ```\n */\nexport function formatSuggestion(result: SuggestionResult | null): string {\n if (result === null) return '';\n\n const sections: string[] = [];\n\n // Tip (before Next)\n if (result.tip) {\n sections.push(result.tip);\n }\n\n const nextSection = formatSection('Next', result.next);\n if (nextSection) sections.push(nextSection);\n\n // Or section (after Next, before See also)\n if (result.or && result.or.items.length > 0) {\n const lines = result.or.items.map((s) =>\n s.description ? ` ${s.command} # ${s.description}` : ` ${s.command}`\n );\n sections.push(`${result.or.label}:\\n${lines.join('\\n')}`);\n }\n\n const seeAlsoSection = formatSection('See also', result.seeAlso);\n if (seeAlsoSection) sections.push(seeAlsoSection);\n\n if (sections.length === 0) return '';\n\n return '\\n' + sections.join('\\n\\n');\n}\n"],"names":[],"mappings":"AAQA,SAAS,cAAc,OAAe,OAA6B;AACjE,MAAI,MAAM,WAAW,EAAG,QAAO;AAG/B,QAAM,gBAAgB,KAAK,IAAI,GAAG,MAAM,IAAI,CAAC,MAAM,EAAE,QAAQ,MAAM,CAAC;AACpE,QAAM,UAAU;AAEhB,QAAM,QAAQ,MAAM,IAAI,CAAC,MAAM;AAC7B,UAAM,SAAS,IAAI,OAAO,gBAAgB,EAAE,QAAQ,SAAS,OAAO;AACpE,WAAO,KAAK,EAAE,OAAO,GAAG,MAAM,KAAK,EAAE,WAAW;AAAA,EAClD,CAAC;AAED,SAAO,GAAG,KAAK;AAAA,EAAM,MAAM,KAAK,IAAI,CAAC;AACvC;AAcO,SAAS,iBAAiB,QAAyC;AACxE,MAAI,WAAW,KAAM,QAAO;AAE5B,QAAM,WAAqB,CAAA;AAG3B,MAAI,OAAO,KAAK;AACd,aAAS,KAAK,OAAO,GAAG;AAAA,EAC1B;AAEA,QAAM,cAAc,cAAc,QAAQ,OAAO,IAAI;AACrD,MAAI,YAAa,UAAS,KAAK,WAAW;AAG1C,MAAI,OAAO,MAAM,OAAO,GAAG,MAAM,SAAS,GAAG;AAC3C,UAAM,QAAQ,OAAO,GAAG,MAAM;AAAA,MAAI,CAAC,MACjC,EAAE,cAAc,KAAK,EAAE,OAAO,SAAS,EAAE,WAAW,KAAK,KAAK,EAAE,OAAO;AAAA,IAAA;AAEzE,aAAS,KAAK,GAAG,OAAO,GAAG,KAAK;AAAA,EAAM,MAAM,KAAK,IAAI,CAAC,EAAE;AAAA,EAC1D;AAEA,QAAM,iBAAiB,cAAc,YAAY,OAAO,OAAO;AAC/D,MAAI,eAAgB,UAAS,KAAK,cAAc;AAEhD,MAAI,SAAS,WAAW,EAAG,QAAO;AAElC,SAAO,OAAO,SAAS,KAAK,MAAM;AACpC;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rules.d.ts","sourceRoot":"","sources":["../../../src/cli/suggestions/rules.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,gBAAgB,EAAkB,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"rules.d.ts","sourceRoot":"","sources":["../../../src/cli/suggestions/rules.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,gBAAgB,EAAkB,MAAM,YAAY,CAAC;AAgctF;;;GAGG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,iBAAiB,GAAG,gBAAgB,GAAG,IAAI,CAM7E"}
|
|
@@ -11,18 +11,31 @@ const queryValidateRule = (ctx) => {
|
|
|
11
11
|
if (ctx.command !== "query validate") return null;
|
|
12
12
|
const file = ctx.queryFile ?? "<query-file>";
|
|
13
13
|
if (ctx.validationSuccess === false) {
|
|
14
|
+
const next2 = [{ command: `$EDITOR ${file}`, description: "Fix errors and re-validate" }];
|
|
15
|
+
if (ctx.hasSchemaLink === false) {
|
|
16
|
+
return {
|
|
17
|
+
next: next2,
|
|
18
|
+
or: {
|
|
19
|
+
label: "Or create a new query from the template",
|
|
20
|
+
items: [{ command: "search-hub query init -o query.yaml", description: "" }]
|
|
21
|
+
},
|
|
22
|
+
seeAlso: []
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
return { next: next2, seeAlso: [] };
|
|
26
|
+
}
|
|
27
|
+
const next = [
|
|
28
|
+
{ command: `search-hub search ${file} --dry-run`, description: "Check DB translations" },
|
|
29
|
+
{ command: `search-hub search ${file} --preview`, description: "Preview hit counts + sample titles" }
|
|
30
|
+
];
|
|
31
|
+
if (ctx.hasSchemaLink === false) {
|
|
14
32
|
return {
|
|
15
|
-
|
|
33
|
+
tip: "Tip: Start from a template to get $schema support and usage examples:\n search-hub query init -o query.yaml",
|
|
34
|
+
next,
|
|
16
35
|
seeAlso: []
|
|
17
36
|
};
|
|
18
37
|
}
|
|
19
|
-
return {
|
|
20
|
-
next: [
|
|
21
|
-
{ command: `search-hub search ${file} --dry-run`, description: "Check DB translations" },
|
|
22
|
-
{ command: `search-hub search ${file} --preview`, description: "Preview hit counts + sample titles" }
|
|
23
|
-
],
|
|
24
|
-
seeAlso: []
|
|
25
|
-
};
|
|
38
|
+
return { next, seeAlso: [] };
|
|
26
39
|
};
|
|
27
40
|
const queryTranslateRule = (ctx) => {
|
|
28
41
|
if (ctx.command !== "query translate") return null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rules.js","sources":["../../../src/cli/suggestions/rules.ts"],"sourcesContent":["import type { SuggestionContext, SuggestionResult, SuggestionRule } from './types.js';\nimport { computeBatchContinuation, generateReviewNextSteps } from '../commands/review/next-steps.js';\n\n// Phase 1: Query Preparation rules\n\nconst queryInitRule: SuggestionRule = (ctx) => {\n if (ctx.command !== 'query init') return null;\n const file = ctx.outputFile ?? 'query.yaml';\n return {\n next: [{ command: `$EDITOR ${file}`, description: 'Edit your query' }],\n seeAlso: [],\n };\n};\n\nconst queryValidateRule: SuggestionRule = (ctx) => {\n if (ctx.command !== 'query validate') return null;\n const file = ctx.queryFile ?? '<query-file>';\n\n if (ctx.validationSuccess === false) {\n return {\n next: [{ command: `$EDITOR ${file}`, description: 'Fix errors and re-validate' }],\n seeAlso: [],\n };\n }\n\n return {\n next: [\n { command: `search-hub search ${file} --dry-run`, description: 'Check DB translations' },\n { command: `search-hub search ${file} --preview`, description: 'Preview hit counts + sample titles' },\n ],\n seeAlso: [],\n };\n};\n\nconst queryTranslateRule: SuggestionRule = (ctx) => {\n if (ctx.command !== 'query translate') return null;\n const file = ctx.queryFile ?? '<query-file>';\n return {\n next: [\n { command: `search-hub search ${file} --preview`, description: 'Preview hit counts + sample titles' },\n { command: `search-hub search ${file}`, description: 'Execute search' },\n ],\n seeAlso: [],\n };\n};\n\n// Phase 2: Search Execution rules\n\n/**\n * Build search completion suggestions based on session status.\n * Shared by search, search --query, and resume commands.\n */\nfunction searchCompletionSuggestion(ctx: SuggestionContext): SuggestionResult | null {\n const sid = ctx.sessionId ?? '<session-id>';\n\n switch (ctx.sessionStatus) {\n case 'completed': {\n const seeAlso: SuggestionResult['seeAlso'] = [];\n if (ctx.sessionCount !== undefined && ctx.sessionCount > 1) {\n seeAlso.push({\n command: `search-hub diff <other-session> ${sid}`,\n description: 'Compare with another query version',\n });\n }\n return {\n next: [{ command: `search-hub results ${sid}`, description: 'View results' }],\n seeAlso,\n };\n }\n case 'partial':\n return {\n next: [{ command: `search-hub resume ${sid}`, description: 'Retry failed databases' }],\n seeAlso: [],\n };\n case 'failed':\n return {\n next: [\n { command: `search-hub resume ${sid} --retry-failed`, description: 'Retry all databases' },\n { command: `search-hub status ${sid}`, description: 'View error details' },\n ],\n seeAlso: [],\n };\n default:\n return null;\n }\n}\n\nconst searchDryRunRule: SuggestionRule = (ctx) => {\n if (ctx.command !== 'search --dry-run') return null;\n const file = ctx.queryFile ?? '<query-file>';\n return {\n next: [\n { command: `search-hub search ${file} --preview`, description: 'Preview hit counts + sample titles' },\n { command: `search-hub search ${file}`, description: 'Execute search' },\n ],\n seeAlso: [],\n };\n};\n\nconst searchPreviewRule: SuggestionRule = (ctx) => {\n if (ctx.command !== 'search --preview') return null;\n const file = ctx.queryFile ?? '<query-file>';\n return {\n next: [{ command: `search-hub search ${file}`, description: 'Execute full search' }],\n seeAlso: [],\n };\n};\n\nconst searchCountOnlyRule: SuggestionRule = (ctx) => {\n if (ctx.command !== 'search --count-only') return null;\n const file = ctx.queryFile ?? '<query-file>';\n return {\n next: [{ command: `search-hub search ${file}`, description: 'Execute full search' }],\n seeAlso: [],\n };\n};\n\nconst searchDirectQueryRule: SuggestionRule = (ctx) => {\n if (ctx.command !== 'search --query') return null;\n const base = searchCompletionSuggestion(ctx);\n if (base === null) return null;\n return {\n next: base.next,\n seeAlso: [\n ...base.seeAlso,\n { command: 'search-hub query init -o my-search.yaml', description: 'Save as YAML for reproducibility' },\n ],\n };\n};\n\nconst searchRule: SuggestionRule = (ctx) => {\n if (ctx.command !== 'search') return null;\n return searchCompletionSuggestion(ctx);\n};\n\nconst resumeRule: SuggestionRule = (ctx) => {\n if (ctx.command !== 'resume') return null;\n return searchCompletionSuggestion(ctx);\n};\n\n// Phase 3: Result Analysis rules\n\nconst statusRule: SuggestionRule = (ctx) => {\n if (ctx.command !== 'status') return null;\n const sid = ctx.sessionId ?? '<session-id>';\n\n switch (ctx.sessionStatus) {\n case 'completed':\n return {\n next: [{ command: `search-hub results ${sid}`, description: 'View results' }],\n seeAlso: [],\n };\n case 'partial':\n return {\n next: [{ command: `search-hub resume ${sid}`, description: 'Resume search' }],\n seeAlso: [],\n };\n case 'failed':\n return {\n next: [{ command: `search-hub resume ${sid} --retry-failed`, description: 'Retry all databases' }],\n seeAlso: [],\n };\n default:\n return null;\n }\n};\n\n/**\n * Suggestion for results/summary commands - conditional based on reviews.yaml existence.\n */\nfunction resultReviewSuggestion(ctx: SuggestionContext): SuggestionResult {\n const sid = ctx.sessionId ?? '<session-id>';\n if (ctx.hasReviews === true) {\n return {\n next: [{ command: `search-hub review status --session ${sid}`, description: 'Check review progress' }],\n seeAlso: [],\n };\n }\n return {\n next: [{ command: `search-hub review init --session ${sid}`, description: 'Start systematic review' }],\n seeAlso: [],\n };\n}\n\nconst resultsRule: SuggestionRule = (ctx) => {\n if (ctx.command !== 'results') return null;\n return resultReviewSuggestion(ctx);\n};\n\nconst summaryRule: SuggestionRule = (ctx) => {\n if (ctx.command !== 'summary') return null;\n return resultReviewSuggestion(ctx);\n};\n\nconst diffRule: SuggestionRule = (ctx) => {\n if (ctx.command !== 'diff') return null;\n const sid = ctx.sessionId ?? '<session-id>';\n const seeAlso: SuggestionResult['seeAlso'] = [];\n\n // Suggest merge when both sessions have unique articles\n if (ctx.diffAddedCount !== undefined && ctx.diffAddedCount > 0 &&\n ctx.diffRemovedCount !== undefined && ctx.diffRemovedCount > 0) {\n const sid1 = ctx.diffSession1Id ?? '<session-id-1>';\n seeAlso.push({\n command: `search-hub merge ${sid1} ${sid}`,\n description: 'Combine results from both sessions',\n });\n }\n\n seeAlso.push({ command: `search-hub results ${sid}`, description: 'View detailed results' });\n\n return { next: [], seeAlso };\n};\n\nconst mergeRule: SuggestionRule = (ctx) => {\n if (ctx.command !== 'merge') return null;\n const sid = ctx.sessionId ?? '<session-id>';\n return {\n next: [\n { command: `search-hub results ${sid}`, description: 'View merged results' },\n { command: `search-hub summary ${sid}`, description: 'View merge statistics' },\n ],\n seeAlso: [],\n };\n};\n\n// Phase 4: Review Workflow rules\n\nconst reviewInitRule: SuggestionRule = (ctx) => {\n if (ctx.command !== 'review init') return null;\n const sid = ctx.sessionId ?? '<session-id>';\n return {\n next: [\n {\n command: `search-hub review extract --session ${sid} --basis title --name title-screening`,\n description: 'Start title screening',\n },\n ],\n seeAlso: [],\n };\n};\n\nconst reviewStatusRule: SuggestionRule = (ctx) => {\n if (ctx.command !== 'review status') return null;\n const rs = ctx.reviewStatus;\n if (!rs) return null;\n\n return generateReviewNextSteps({\n sessionId: ctx.sessionId ?? '<session-id>',\n statusResult: rs,\n });\n};\n\nconst reviewListRule: SuggestionRule = (ctx) => {\n if (ctx.command !== 'review list') return null;\n const sid = ctx.sessionId ?? '<session-id>';\n return {\n next: [],\n seeAlso: [\n {\n command: `search-hub review extract --session ${sid} --name <name>`,\n description: 'Extract subset for review',\n },\n ],\n };\n};\n\nconst reviewExtractRule: SuggestionRule = (ctx) => {\n if (ctx.command !== 'review extract') return null;\n const sid = ctx.sessionId ?? '<session-id>';\n const name = ctx.extractName ?? '<name>';\n const result: SuggestionResult = {\n next: [\n {\n command: `search-hub review merge --session ${sid} --name ${name}`,\n description: 'Merge review results',\n },\n ],\n seeAlso: [],\n };\n\n // Batch continuation: suggest next batch if --limit was used with remaining articles\n if (\n ctx.extractLimit !== undefined &&\n ctx.extractedCount !== undefined &&\n ctx.totalMatching !== undefined\n ) {\n const batch = computeBatchContinuation({\n sessionId: sid,\n extractName: name !== '<name>' ? name : undefined,\n extractedCount: ctx.extractedCount,\n totalMatching: ctx.totalMatching,\n limit: ctx.extractLimit,\n offset: ctx.extractOffset,\n });\n if (batch) result.seeAlso.push(batch);\n }\n\n return result;\n};\n\nconst reviewMergeRule: SuggestionRule = (ctx) => {\n if (ctx.command !== 'review merge') return null;\n const rs = ctx.reviewStatus;\n if (!rs) {\n // Fallback: suggest status check when reviewStatus not available\n const sid = ctx.sessionId ?? '<session-id>';\n return {\n next: [\n {\n command: `search-hub review status --session ${sid}`,\n description: 'Check progress',\n },\n ],\n seeAlso: [],\n };\n }\n\n return generateReviewNextSteps({\n sessionId: ctx.sessionId ?? '<session-id>',\n statusResult: rs,\n });\n};\n\nconst reviewFinalizeRule: SuggestionRule = (ctx) => {\n if (ctx.command !== 'review finalize') return null;\n const rs = ctx.reviewStatus;\n if (!rs) return null;\n\n return generateReviewNextSteps({\n sessionId: ctx.sessionId ?? '<session-id>',\n statusResult: rs,\n });\n};\n\nconst reviewExportRule: SuggestionRule = (ctx) => {\n if (ctx.command !== 'review export') return null;\n const sid = ctx.sessionId ?? '<session-id>';\n return {\n next: [],\n seeAlso: [\n {\n command: `search-hub register ${sid} --reviewed`,\n description: 'Register with reference-manager',\n },\n ],\n };\n};\n\n// Phase 5: Registration & Export rules\n\nconst exportRule: SuggestionRule = (ctx) => {\n if (ctx.command !== 'export') return null;\n const sid = ctx.sessionId ?? '<session-id>';\n if (ctx.hasReviews === false) {\n return {\n next: [],\n seeAlso: [\n {\n command: `search-hub review init --session ${sid}`,\n description: 'Start review workflow',\n },\n ],\n };\n }\n return { next: [], seeAlso: [] };\n};\n\nconst registerRule: SuggestionRule = (ctx) => {\n if (ctx.command !== 'register') return null;\n const sid = ctx.sessionId ?? '<session-id>';\n if (ctx.hasReviews === false) {\n return {\n next: [],\n seeAlso: [\n {\n command: `search-hub review init --session ${sid}`,\n description: 'Start systematic review',\n },\n ],\n };\n }\n // Terminal state: no suggestions\n return null;\n};\n\nconst notesRule: SuggestionRule = (ctx) => {\n if (ctx.command !== 'notes add' && ctx.command !== 'notes assess') return null;\n const sid = ctx.sessionId ?? '<session-id>';\n return {\n next: [],\n seeAlso: [{ command: `search-hub notes list ${sid}`, description: 'View notes' }],\n };\n};\n\n/**\n * All suggestion rules in evaluation order.\n */\nconst rules: SuggestionRule[] = [\n // Phase 1\n queryInitRule,\n queryValidateRule,\n queryTranslateRule,\n // Phase 2\n searchDryRunRule,\n searchPreviewRule,\n searchCountOnlyRule,\n searchDirectQueryRule,\n searchRule,\n resumeRule,\n // Phase 3\n statusRule,\n resultsRule,\n summaryRule,\n diffRule,\n mergeRule,\n // Phase 4\n reviewInitRule,\n reviewStatusRule,\n reviewListRule,\n reviewExtractRule,\n reviewMergeRule,\n reviewFinalizeRule,\n reviewExportRule,\n // Phase 5\n exportRule,\n registerRule,\n notesRule,\n];\n\n/**\n * Evaluate suggestion rules for the given context.\n * Returns the first matching rule's result, or null if no rules match.\n */\nexport function getSuggestion(ctx: SuggestionContext): SuggestionResult | null {\n for (const rule of rules) {\n const result = rule(ctx);\n if (result !== null) return result;\n }\n return null;\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"rules.js","sources":["../../../src/cli/suggestions/rules.ts"],"sourcesContent":["import type { SuggestionContext, SuggestionResult, SuggestionRule } from './types.js';\nimport { computeBatchContinuation, generateReviewNextSteps } from '../commands/review/next-steps.js';\n\n// Phase 1: Query Preparation rules\n\nconst queryInitRule: SuggestionRule = (ctx) => {\n if (ctx.command !== 'query init') return null;\n const file = ctx.outputFile ?? 'query.yaml';\n return {\n next: [{ command: `$EDITOR ${file}`, description: 'Edit your query' }],\n seeAlso: [],\n };\n};\n\nconst queryValidateRule: SuggestionRule = (ctx) => {\n if (ctx.command !== 'query validate') return null;\n const file = ctx.queryFile ?? '<query-file>';\n\n if (ctx.validationSuccess === false) {\n const next = [{ command: `$EDITOR ${file}`, description: 'Fix errors and re-validate' }];\n\n if (ctx.hasSchemaLink === false) {\n return {\n next,\n or: {\n label: 'Or create a new query from the template',\n items: [{ command: 'search-hub query init -o query.yaml', description: '' }],\n },\n seeAlso: [],\n };\n }\n\n return { next, seeAlso: [] };\n }\n\n const next = [\n { command: `search-hub search ${file} --dry-run`, description: 'Check DB translations' },\n { command: `search-hub search ${file} --preview`, description: 'Preview hit counts + sample titles' },\n ];\n\n if (ctx.hasSchemaLink === false) {\n return {\n tip: 'Tip: Start from a template to get $schema support and usage examples:\\n'\n + ' search-hub query init -o query.yaml',\n next,\n seeAlso: [],\n };\n }\n\n return { next, seeAlso: [] };\n};\n\nconst queryTranslateRule: SuggestionRule = (ctx) => {\n if (ctx.command !== 'query translate') return null;\n const file = ctx.queryFile ?? '<query-file>';\n return {\n next: [\n { command: `search-hub search ${file} --preview`, description: 'Preview hit counts + sample titles' },\n { command: `search-hub search ${file}`, description: 'Execute search' },\n ],\n seeAlso: [],\n };\n};\n\n// Phase 2: Search Execution rules\n\n/**\n * Build search completion suggestions based on session status.\n * Shared by search, search --query, and resume commands.\n */\nfunction searchCompletionSuggestion(ctx: SuggestionContext): SuggestionResult | null {\n const sid = ctx.sessionId ?? '<session-id>';\n\n switch (ctx.sessionStatus) {\n case 'completed': {\n const seeAlso: SuggestionResult['seeAlso'] = [];\n if (ctx.sessionCount !== undefined && ctx.sessionCount > 1) {\n seeAlso.push({\n command: `search-hub diff <other-session> ${sid}`,\n description: 'Compare with another query version',\n });\n }\n return {\n next: [{ command: `search-hub results ${sid}`, description: 'View results' }],\n seeAlso,\n };\n }\n case 'partial':\n return {\n next: [{ command: `search-hub resume ${sid}`, description: 'Retry failed databases' }],\n seeAlso: [],\n };\n case 'failed':\n return {\n next: [\n { command: `search-hub resume ${sid} --retry-failed`, description: 'Retry all databases' },\n { command: `search-hub status ${sid}`, description: 'View error details' },\n ],\n seeAlso: [],\n };\n default:\n return null;\n }\n}\n\nconst searchDryRunRule: SuggestionRule = (ctx) => {\n if (ctx.command !== 'search --dry-run') return null;\n const file = ctx.queryFile ?? '<query-file>';\n return {\n next: [\n { command: `search-hub search ${file} --preview`, description: 'Preview hit counts + sample titles' },\n { command: `search-hub search ${file}`, description: 'Execute search' },\n ],\n seeAlso: [],\n };\n};\n\nconst searchPreviewRule: SuggestionRule = (ctx) => {\n if (ctx.command !== 'search --preview') return null;\n const file = ctx.queryFile ?? '<query-file>';\n return {\n next: [{ command: `search-hub search ${file}`, description: 'Execute full search' }],\n seeAlso: [],\n };\n};\n\nconst searchCountOnlyRule: SuggestionRule = (ctx) => {\n if (ctx.command !== 'search --count-only') return null;\n const file = ctx.queryFile ?? '<query-file>';\n return {\n next: [{ command: `search-hub search ${file}`, description: 'Execute full search' }],\n seeAlso: [],\n };\n};\n\nconst searchDirectQueryRule: SuggestionRule = (ctx) => {\n if (ctx.command !== 'search --query') return null;\n const base = searchCompletionSuggestion(ctx);\n if (base === null) return null;\n return {\n next: base.next,\n seeAlso: [\n ...base.seeAlso,\n { command: 'search-hub query init -o my-search.yaml', description: 'Save as YAML for reproducibility' },\n ],\n };\n};\n\nconst searchRule: SuggestionRule = (ctx) => {\n if (ctx.command !== 'search') return null;\n return searchCompletionSuggestion(ctx);\n};\n\nconst resumeRule: SuggestionRule = (ctx) => {\n if (ctx.command !== 'resume') return null;\n return searchCompletionSuggestion(ctx);\n};\n\n// Phase 3: Result Analysis rules\n\nconst statusRule: SuggestionRule = (ctx) => {\n if (ctx.command !== 'status') return null;\n const sid = ctx.sessionId ?? '<session-id>';\n\n switch (ctx.sessionStatus) {\n case 'completed':\n return {\n next: [{ command: `search-hub results ${sid}`, description: 'View results' }],\n seeAlso: [],\n };\n case 'partial':\n return {\n next: [{ command: `search-hub resume ${sid}`, description: 'Resume search' }],\n seeAlso: [],\n };\n case 'failed':\n return {\n next: [{ command: `search-hub resume ${sid} --retry-failed`, description: 'Retry all databases' }],\n seeAlso: [],\n };\n default:\n return null;\n }\n};\n\n/**\n * Suggestion for results/summary commands - conditional based on reviews.yaml existence.\n */\nfunction resultReviewSuggestion(ctx: SuggestionContext): SuggestionResult {\n const sid = ctx.sessionId ?? '<session-id>';\n if (ctx.hasReviews === true) {\n return {\n next: [{ command: `search-hub review status --session ${sid}`, description: 'Check review progress' }],\n seeAlso: [],\n };\n }\n return {\n next: [{ command: `search-hub review init --session ${sid}`, description: 'Start systematic review' }],\n seeAlso: [],\n };\n}\n\nconst resultsRule: SuggestionRule = (ctx) => {\n if (ctx.command !== 'results') return null;\n return resultReviewSuggestion(ctx);\n};\n\nconst summaryRule: SuggestionRule = (ctx) => {\n if (ctx.command !== 'summary') return null;\n return resultReviewSuggestion(ctx);\n};\n\nconst diffRule: SuggestionRule = (ctx) => {\n if (ctx.command !== 'diff') return null;\n const sid = ctx.sessionId ?? '<session-id>';\n const seeAlso: SuggestionResult['seeAlso'] = [];\n\n // Suggest merge when both sessions have unique articles\n if (ctx.diffAddedCount !== undefined && ctx.diffAddedCount > 0 &&\n ctx.diffRemovedCount !== undefined && ctx.diffRemovedCount > 0) {\n const sid1 = ctx.diffSession1Id ?? '<session-id-1>';\n seeAlso.push({\n command: `search-hub merge ${sid1} ${sid}`,\n description: 'Combine results from both sessions',\n });\n }\n\n seeAlso.push({ command: `search-hub results ${sid}`, description: 'View detailed results' });\n\n return { next: [], seeAlso };\n};\n\nconst mergeRule: SuggestionRule = (ctx) => {\n if (ctx.command !== 'merge') return null;\n const sid = ctx.sessionId ?? '<session-id>';\n return {\n next: [\n { command: `search-hub results ${sid}`, description: 'View merged results' },\n { command: `search-hub summary ${sid}`, description: 'View merge statistics' },\n ],\n seeAlso: [],\n };\n};\n\n// Phase 4: Review Workflow rules\n\nconst reviewInitRule: SuggestionRule = (ctx) => {\n if (ctx.command !== 'review init') return null;\n const sid = ctx.sessionId ?? '<session-id>';\n return {\n next: [\n {\n command: `search-hub review extract --session ${sid} --basis title --name title-screening`,\n description: 'Start title screening',\n },\n ],\n seeAlso: [],\n };\n};\n\nconst reviewStatusRule: SuggestionRule = (ctx) => {\n if (ctx.command !== 'review status') return null;\n const rs = ctx.reviewStatus;\n if (!rs) return null;\n\n return generateReviewNextSteps({\n sessionId: ctx.sessionId ?? '<session-id>',\n statusResult: rs,\n });\n};\n\nconst reviewListRule: SuggestionRule = (ctx) => {\n if (ctx.command !== 'review list') return null;\n const sid = ctx.sessionId ?? '<session-id>';\n return {\n next: [],\n seeAlso: [\n {\n command: `search-hub review extract --session ${sid} --name <name>`,\n description: 'Extract subset for review',\n },\n ],\n };\n};\n\nconst reviewExtractRule: SuggestionRule = (ctx) => {\n if (ctx.command !== 'review extract') return null;\n const sid = ctx.sessionId ?? '<session-id>';\n const name = ctx.extractName ?? '<name>';\n const result: SuggestionResult = {\n next: [\n {\n command: `search-hub review merge --session ${sid} --name ${name}`,\n description: 'Merge review results',\n },\n ],\n seeAlso: [],\n };\n\n // Batch continuation: suggest next batch if --limit was used with remaining articles\n if (\n ctx.extractLimit !== undefined &&\n ctx.extractedCount !== undefined &&\n ctx.totalMatching !== undefined\n ) {\n const batch = computeBatchContinuation({\n sessionId: sid,\n extractName: name !== '<name>' ? name : undefined,\n extractedCount: ctx.extractedCount,\n totalMatching: ctx.totalMatching,\n limit: ctx.extractLimit,\n offset: ctx.extractOffset,\n });\n if (batch) result.seeAlso.push(batch);\n }\n\n return result;\n};\n\nconst reviewMergeRule: SuggestionRule = (ctx) => {\n if (ctx.command !== 'review merge') return null;\n const rs = ctx.reviewStatus;\n if (!rs) {\n // Fallback: suggest status check when reviewStatus not available\n const sid = ctx.sessionId ?? '<session-id>';\n return {\n next: [\n {\n command: `search-hub review status --session ${sid}`,\n description: 'Check progress',\n },\n ],\n seeAlso: [],\n };\n }\n\n return generateReviewNextSteps({\n sessionId: ctx.sessionId ?? '<session-id>',\n statusResult: rs,\n });\n};\n\nconst reviewFinalizeRule: SuggestionRule = (ctx) => {\n if (ctx.command !== 'review finalize') return null;\n const rs = ctx.reviewStatus;\n if (!rs) return null;\n\n return generateReviewNextSteps({\n sessionId: ctx.sessionId ?? '<session-id>',\n statusResult: rs,\n });\n};\n\nconst reviewExportRule: SuggestionRule = (ctx) => {\n if (ctx.command !== 'review export') return null;\n const sid = ctx.sessionId ?? '<session-id>';\n return {\n next: [],\n seeAlso: [\n {\n command: `search-hub register ${sid} --reviewed`,\n description: 'Register with reference-manager',\n },\n ],\n };\n};\n\n// Phase 5: Registration & Export rules\n\nconst exportRule: SuggestionRule = (ctx) => {\n if (ctx.command !== 'export') return null;\n const sid = ctx.sessionId ?? '<session-id>';\n if (ctx.hasReviews === false) {\n return {\n next: [],\n seeAlso: [\n {\n command: `search-hub review init --session ${sid}`,\n description: 'Start review workflow',\n },\n ],\n };\n }\n return { next: [], seeAlso: [] };\n};\n\nconst registerRule: SuggestionRule = (ctx) => {\n if (ctx.command !== 'register') return null;\n const sid = ctx.sessionId ?? '<session-id>';\n if (ctx.hasReviews === false) {\n return {\n next: [],\n seeAlso: [\n {\n command: `search-hub review init --session ${sid}`,\n description: 'Start systematic review',\n },\n ],\n };\n }\n // Terminal state: no suggestions\n return null;\n};\n\nconst notesRule: SuggestionRule = (ctx) => {\n if (ctx.command !== 'notes add' && ctx.command !== 'notes assess') return null;\n const sid = ctx.sessionId ?? '<session-id>';\n return {\n next: [],\n seeAlso: [{ command: `search-hub notes list ${sid}`, description: 'View notes' }],\n };\n};\n\n/**\n * All suggestion rules in evaluation order.\n */\nconst rules: SuggestionRule[] = [\n // Phase 1\n queryInitRule,\n queryValidateRule,\n queryTranslateRule,\n // Phase 2\n searchDryRunRule,\n searchPreviewRule,\n searchCountOnlyRule,\n searchDirectQueryRule,\n searchRule,\n resumeRule,\n // Phase 3\n statusRule,\n resultsRule,\n summaryRule,\n diffRule,\n mergeRule,\n // Phase 4\n reviewInitRule,\n reviewStatusRule,\n reviewListRule,\n reviewExtractRule,\n reviewMergeRule,\n reviewFinalizeRule,\n reviewExportRule,\n // Phase 5\n exportRule,\n registerRule,\n notesRule,\n];\n\n/**\n * Evaluate suggestion rules for the given context.\n * Returns the first matching rule's result, or null if no rules match.\n */\nexport function getSuggestion(ctx: SuggestionContext): SuggestionResult | null {\n for (const rule of rules) {\n const result = rule(ctx);\n if (result !== null) return result;\n }\n return null;\n}\n"],"names":["next"],"mappings":";AAKA,MAAM,gBAAgC,CAAC,QAAQ;AAC7C,MAAI,IAAI,YAAY,aAAc,QAAO;AACzC,QAAM,OAAO,IAAI,cAAc;AAC/B,SAAO;AAAA,IACL,MAAM,CAAC,EAAE,SAAS,WAAW,IAAI,IAAI,aAAa,mBAAmB;AAAA,IACrE,SAAS,CAAA;AAAA,EAAC;AAEd;AAEA,MAAM,oBAAoC,CAAC,QAAQ;AACjD,MAAI,IAAI,YAAY,iBAAkB,QAAO;AAC7C,QAAM,OAAO,IAAI,aAAa;AAE9B,MAAI,IAAI,sBAAsB,OAAO;AACnC,UAAMA,QAAO,CAAC,EAAE,SAAS,WAAW,IAAI,IAAI,aAAa,8BAA8B;AAEvF,QAAI,IAAI,kBAAkB,OAAO;AAC/B,aAAO;AAAA,QACL,MAAAA;AAAAA,QACA,IAAI;AAAA,UACF,OAAO;AAAA,UACP,OAAO,CAAC,EAAE,SAAS,uCAAuC,aAAa,IAAI;AAAA,QAAA;AAAA,QAE7E,SAAS,CAAA;AAAA,MAAC;AAAA,IAEd;AAEA,WAAO,EAAE,MAAAA,OAAM,SAAS,CAAA,EAAC;AAAA,EAC3B;AAEA,QAAM,OAAO;AAAA,IACX,EAAE,SAAS,qBAAqB,IAAI,cAAc,aAAa,wBAAA;AAAA,IAC/D,EAAE,SAAS,qBAAqB,IAAI,cAAc,aAAa,qCAAA;AAAA,EAAqC;AAGtG,MAAI,IAAI,kBAAkB,OAAO;AAC/B,WAAO;AAAA,MACL,KAAK;AAAA,MAEL;AAAA,MACA,SAAS,CAAA;AAAA,IAAC;AAAA,EAEd;AAEA,SAAO,EAAE,MAAM,SAAS,GAAC;AAC3B;AAEA,MAAM,qBAAqC,CAAC,QAAQ;AAClD,MAAI,IAAI,YAAY,kBAAmB,QAAO;AAC9C,QAAM,OAAO,IAAI,aAAa;AAC9B,SAAO;AAAA,IACL,MAAM;AAAA,MACJ,EAAE,SAAS,qBAAqB,IAAI,cAAc,aAAa,qCAAA;AAAA,MAC/D,EAAE,SAAS,qBAAqB,IAAI,IAAI,aAAa,iBAAA;AAAA,IAAiB;AAAA,IAExE,SAAS,CAAA;AAAA,EAAC;AAEd;AAQA,SAAS,2BAA2B,KAAiD;AACnF,QAAM,MAAM,IAAI,aAAa;AAE7B,UAAQ,IAAI,eAAA;AAAA,IACV,KAAK,aAAa;AAChB,YAAM,UAAuC,CAAA;AAC7C,UAAI,IAAI,iBAAiB,UAAa,IAAI,eAAe,GAAG;AAC1D,gBAAQ,KAAK;AAAA,UACX,SAAS,mCAAmC,GAAG;AAAA,UAC/C,aAAa;AAAA,QAAA,CACd;AAAA,MACH;AACA,aAAO;AAAA,QACL,MAAM,CAAC,EAAE,SAAS,sBAAsB,GAAG,IAAI,aAAa,gBAAgB;AAAA,QAC5E;AAAA,MAAA;AAAA,IAEJ;AAAA,IACA,KAAK;AACH,aAAO;AAAA,QACL,MAAM,CAAC,EAAE,SAAS,qBAAqB,GAAG,IAAI,aAAa,0BAA0B;AAAA,QACrF,SAAS,CAAA;AAAA,MAAC;AAAA,IAEd,KAAK;AACH,aAAO;AAAA,QACL,MAAM;AAAA,UACJ,EAAE,SAAS,qBAAqB,GAAG,mBAAmB,aAAa,sBAAA;AAAA,UACnE,EAAE,SAAS,qBAAqB,GAAG,IAAI,aAAa,qBAAA;AAAA,QAAqB;AAAA,QAE3E,SAAS,CAAA;AAAA,MAAC;AAAA,IAEd;AACE,aAAO;AAAA,EAAA;AAEb;AAEA,MAAM,mBAAmC,CAAC,QAAQ;AAChD,MAAI,IAAI,YAAY,mBAAoB,QAAO;AAC/C,QAAM,OAAO,IAAI,aAAa;AAC9B,SAAO;AAAA,IACL,MAAM;AAAA,MACJ,EAAE,SAAS,qBAAqB,IAAI,cAAc,aAAa,qCAAA;AAAA,MAC/D,EAAE,SAAS,qBAAqB,IAAI,IAAI,aAAa,iBAAA;AAAA,IAAiB;AAAA,IAExE,SAAS,CAAA;AAAA,EAAC;AAEd;AAEA,MAAM,oBAAoC,CAAC,QAAQ;AACjD,MAAI,IAAI,YAAY,mBAAoB,QAAO;AAC/C,QAAM,OAAO,IAAI,aAAa;AAC9B,SAAO;AAAA,IACL,MAAM,CAAC,EAAE,SAAS,qBAAqB,IAAI,IAAI,aAAa,uBAAuB;AAAA,IACnF,SAAS,CAAA;AAAA,EAAC;AAEd;AAEA,MAAM,sBAAsC,CAAC,QAAQ;AACnD,MAAI,IAAI,YAAY,sBAAuB,QAAO;AAClD,QAAM,OAAO,IAAI,aAAa;AAC9B,SAAO;AAAA,IACL,MAAM,CAAC,EAAE,SAAS,qBAAqB,IAAI,IAAI,aAAa,uBAAuB;AAAA,IACnF,SAAS,CAAA;AAAA,EAAC;AAEd;AAEA,MAAM,wBAAwC,CAAC,QAAQ;AACrD,MAAI,IAAI,YAAY,iBAAkB,QAAO;AAC7C,QAAM,OAAO,2BAA2B,GAAG;AAC3C,MAAI,SAAS,KAAM,QAAO;AAC1B,SAAO;AAAA,IACL,MAAM,KAAK;AAAA,IACX,SAAS;AAAA,MACP,GAAG,KAAK;AAAA,MACR,EAAE,SAAS,2CAA2C,aAAa,mCAAA;AAAA,IAAmC;AAAA,EACxG;AAEJ;AAEA,MAAM,aAA6B,CAAC,QAAQ;AAC1C,MAAI,IAAI,YAAY,SAAU,QAAO;AACrC,SAAO,2BAA2B,GAAG;AACvC;AAEA,MAAM,aAA6B,CAAC,QAAQ;AAC1C,MAAI,IAAI,YAAY,SAAU,QAAO;AACrC,SAAO,2BAA2B,GAAG;AACvC;AAIA,MAAM,aAA6B,CAAC,QAAQ;AAC1C,MAAI,IAAI,YAAY,SAAU,QAAO;AACrC,QAAM,MAAM,IAAI,aAAa;AAE7B,UAAQ,IAAI,eAAA;AAAA,IACV,KAAK;AACH,aAAO;AAAA,QACL,MAAM,CAAC,EAAE,SAAS,sBAAsB,GAAG,IAAI,aAAa,gBAAgB;AAAA,QAC5E,SAAS,CAAA;AAAA,MAAC;AAAA,IAEd,KAAK;AACH,aAAO;AAAA,QACL,MAAM,CAAC,EAAE,SAAS,qBAAqB,GAAG,IAAI,aAAa,iBAAiB;AAAA,QAC5E,SAAS,CAAA;AAAA,MAAC;AAAA,IAEd,KAAK;AACH,aAAO;AAAA,QACL,MAAM,CAAC,EAAE,SAAS,qBAAqB,GAAG,mBAAmB,aAAa,uBAAuB;AAAA,QACjG,SAAS,CAAA;AAAA,MAAC;AAAA,IAEd;AACE,aAAO;AAAA,EAAA;AAEb;AAKA,SAAS,uBAAuB,KAA0C;AACxE,QAAM,MAAM,IAAI,aAAa;AAC7B,MAAI,IAAI,eAAe,MAAM;AAC3B,WAAO;AAAA,MACL,MAAM,CAAC,EAAE,SAAS,sCAAsC,GAAG,IAAI,aAAa,yBAAyB;AAAA,MACrG,SAAS,CAAA;AAAA,IAAC;AAAA,EAEd;AACA,SAAO;AAAA,IACL,MAAM,CAAC,EAAE,SAAS,oCAAoC,GAAG,IAAI,aAAa,2BAA2B;AAAA,IACrG,SAAS,CAAA;AAAA,EAAC;AAEd;AAEA,MAAM,cAA8B,CAAC,QAAQ;AAC3C,MAAI,IAAI,YAAY,UAAW,QAAO;AACtC,SAAO,uBAAuB,GAAG;AACnC;AAEA,MAAM,cAA8B,CAAC,QAAQ;AAC3C,MAAI,IAAI,YAAY,UAAW,QAAO;AACtC,SAAO,uBAAuB,GAAG;AACnC;AAEA,MAAM,WAA2B,CAAC,QAAQ;AACxC,MAAI,IAAI,YAAY,OAAQ,QAAO;AACnC,QAAM,MAAM,IAAI,aAAa;AAC7B,QAAM,UAAuC,CAAA;AAG7C,MAAI,IAAI,mBAAmB,UAAa,IAAI,iBAAiB,KACzD,IAAI,qBAAqB,UAAa,IAAI,mBAAmB,GAAG;AAClE,UAAM,OAAO,IAAI,kBAAkB;AACnC,YAAQ,KAAK;AAAA,MACX,SAAS,oBAAoB,IAAI,IAAI,GAAG;AAAA,MACxC,aAAa;AAAA,IAAA,CACd;AAAA,EACH;AAEA,UAAQ,KAAK,EAAE,SAAS,sBAAsB,GAAG,IAAI,aAAa,yBAAyB;AAE3F,SAAO,EAAE,MAAM,CAAA,GAAI,QAAA;AACrB;AAEA,MAAM,YAA4B,CAAC,QAAQ;AACzC,MAAI,IAAI,YAAY,QAAS,QAAO;AACpC,QAAM,MAAM,IAAI,aAAa;AAC7B,SAAO;AAAA,IACL,MAAM;AAAA,MACJ,EAAE,SAAS,sBAAsB,GAAG,IAAI,aAAa,sBAAA;AAAA,MACrD,EAAE,SAAS,sBAAsB,GAAG,IAAI,aAAa,wBAAA;AAAA,IAAwB;AAAA,IAE/E,SAAS,CAAA;AAAA,EAAC;AAEd;AAIA,MAAM,iBAAiC,CAAC,QAAQ;AAC9C,MAAI,IAAI,YAAY,cAAe,QAAO;AAC1C,QAAM,MAAM,IAAI,aAAa;AAC7B,SAAO;AAAA,IACL,MAAM;AAAA,MACJ;AAAA,QACE,SAAS,uCAAuC,GAAG;AAAA,QACnD,aAAa;AAAA,MAAA;AAAA,IACf;AAAA,IAEF,SAAS,CAAA;AAAA,EAAC;AAEd;AAEA,MAAM,mBAAmC,CAAC,QAAQ;AAChD,MAAI,IAAI,YAAY,gBAAiB,QAAO;AAC5C,QAAM,KAAK,IAAI;AACf,MAAI,CAAC,GAAI,QAAO;AAEhB,SAAO,wBAAwB;AAAA,IAC7B,WAAW,IAAI,aAAa;AAAA,IAC5B,cAAc;AAAA,EAAA,CACf;AACH;AAEA,MAAM,iBAAiC,CAAC,QAAQ;AAC9C,MAAI,IAAI,YAAY,cAAe,QAAO;AAC1C,QAAM,MAAM,IAAI,aAAa;AAC7B,SAAO;AAAA,IACL,MAAM,CAAA;AAAA,IACN,SAAS;AAAA,MACP;AAAA,QACE,SAAS,uCAAuC,GAAG;AAAA,QACnD,aAAa;AAAA,MAAA;AAAA,IACf;AAAA,EACF;AAEJ;AAEA,MAAM,oBAAoC,CAAC,QAAQ;AACjD,MAAI,IAAI,YAAY,iBAAkB,QAAO;AAC7C,QAAM,MAAM,IAAI,aAAa;AAC7B,QAAM,OAAO,IAAI,eAAe;AAChC,QAAM,SAA2B;AAAA,IAC/B,MAAM;AAAA,MACJ;AAAA,QACE,SAAS,qCAAqC,GAAG,WAAW,IAAI;AAAA,QAChE,aAAa;AAAA,MAAA;AAAA,IACf;AAAA,IAEF,SAAS,CAAA;AAAA,EAAC;AAIZ,MACE,IAAI,iBAAiB,UACrB,IAAI,mBAAmB,UACvB,IAAI,kBAAkB,QACtB;AACA,UAAM,QAAQ,yBAAyB;AAAA,MACrC,WAAW;AAAA,MACX,aAAa,SAAS,WAAW,OAAO;AAAA,MACxC,gBAAgB,IAAI;AAAA,MACpB,eAAe,IAAI;AAAA,MACnB,OAAO,IAAI;AAAA,MACX,QAAQ,IAAI;AAAA,IAAA,CACb;AACD,QAAI,MAAO,QAAO,QAAQ,KAAK,KAAK;AAAA,EACtC;AAEA,SAAO;AACT;AAEA,MAAM,kBAAkC,CAAC,QAAQ;AAC/C,MAAI,IAAI,YAAY,eAAgB,QAAO;AAC3C,QAAM,KAAK,IAAI;AACf,MAAI,CAAC,IAAI;AAEP,UAAM,MAAM,IAAI,aAAa;AAC7B,WAAO;AAAA,MACL,MAAM;AAAA,QACJ;AAAA,UACE,SAAS,sCAAsC,GAAG;AAAA,UAClD,aAAa;AAAA,QAAA;AAAA,MACf;AAAA,MAEF,SAAS,CAAA;AAAA,IAAC;AAAA,EAEd;AAEA,SAAO,wBAAwB;AAAA,IAC7B,WAAW,IAAI,aAAa;AAAA,IAC5B,cAAc;AAAA,EAAA,CACf;AACH;AAEA,MAAM,qBAAqC,CAAC,QAAQ;AAClD,MAAI,IAAI,YAAY,kBAAmB,QAAO;AAC9C,QAAM,KAAK,IAAI;AACf,MAAI,CAAC,GAAI,QAAO;AAEhB,SAAO,wBAAwB;AAAA,IAC7B,WAAW,IAAI,aAAa;AAAA,IAC5B,cAAc;AAAA,EAAA,CACf;AACH;AAEA,MAAM,mBAAmC,CAAC,QAAQ;AAChD,MAAI,IAAI,YAAY,gBAAiB,QAAO;AAC5C,QAAM,MAAM,IAAI,aAAa;AAC7B,SAAO;AAAA,IACL,MAAM,CAAA;AAAA,IACN,SAAS;AAAA,MACP;AAAA,QACE,SAAS,uBAAuB,GAAG;AAAA,QACnC,aAAa;AAAA,MAAA;AAAA,IACf;AAAA,EACF;AAEJ;AAIA,MAAM,aAA6B,CAAC,QAAQ;AAC1C,MAAI,IAAI,YAAY,SAAU,QAAO;AACrC,QAAM,MAAM,IAAI,aAAa;AAC7B,MAAI,IAAI,eAAe,OAAO;AAC5B,WAAO;AAAA,MACL,MAAM,CAAA;AAAA,MACN,SAAS;AAAA,QACP;AAAA,UACE,SAAS,oCAAoC,GAAG;AAAA,UAChD,aAAa;AAAA,QAAA;AAAA,MACf;AAAA,IACF;AAAA,EAEJ;AACA,SAAO,EAAE,MAAM,IAAI,SAAS,CAAA,EAAC;AAC/B;AAEA,MAAM,eAA+B,CAAC,QAAQ;AAC5C,MAAI,IAAI,YAAY,WAAY,QAAO;AACvC,QAAM,MAAM,IAAI,aAAa;AAC7B,MAAI,IAAI,eAAe,OAAO;AAC5B,WAAO;AAAA,MACL,MAAM,CAAA;AAAA,MACN,SAAS;AAAA,QACP;AAAA,UACE,SAAS,oCAAoC,GAAG;AAAA,UAChD,aAAa;AAAA,QAAA;AAAA,MACf;AAAA,IACF;AAAA,EAEJ;AAEA,SAAO;AACT;AAEA,MAAM,YAA4B,CAAC,QAAQ;AACzC,MAAI,IAAI,YAAY,eAAe,IAAI,YAAY,eAAgB,QAAO;AAC1E,QAAM,MAAM,IAAI,aAAa;AAC7B,SAAO;AAAA,IACL,MAAM,CAAA;AAAA,IACN,SAAS,CAAC,EAAE,SAAS,yBAAyB,GAAG,IAAI,aAAa,aAAA,CAAc;AAAA,EAAA;AAEpF;AAKA,MAAM,QAA0B;AAAA;AAAA,EAE9B;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AACF;AAMO,SAAS,cAAc,KAAiD;AAC7E,aAAW,QAAQ,OAAO;AACxB,UAAM,SAAS,KAAK,GAAG;AACvB,QAAI,WAAW,KAAM,QAAO;AAAA,EAC9B;AACA,SAAO;AACT;"}
|
|
@@ -17,6 +17,15 @@ export interface SuggestionResult {
|
|
|
17
17
|
next: Suggestion[];
|
|
18
18
|
/** Alternative paths (0-2 items) */
|
|
19
19
|
seeAlso: Suggestion[];
|
|
20
|
+
/** Tip displayed before Next (plain text) */
|
|
21
|
+
tip?: string;
|
|
22
|
+
/** Alternative section displayed after Next, before See also */
|
|
23
|
+
or?: {
|
|
24
|
+
/** Section label (e.g. "Or create a new query from the template") */
|
|
25
|
+
label: string;
|
|
26
|
+
/** Command items */
|
|
27
|
+
items: Suggestion[];
|
|
28
|
+
};
|
|
20
29
|
}
|
|
21
30
|
/**
|
|
22
31
|
* Context passed to suggestion rules for evaluation.
|
|
@@ -50,6 +59,8 @@ export interface SuggestionContext {
|
|
|
50
59
|
outputFile?: string | undefined;
|
|
51
60
|
/** Whether validation succeeded (for query validate) */
|
|
52
61
|
validationSuccess?: boolean | undefined;
|
|
62
|
+
/** Whether the query file has a $schema link */
|
|
63
|
+
hasSchemaLink?: boolean | undefined;
|
|
53
64
|
/** Number of added articles in diff result */
|
|
54
65
|
diffAddedCount?: number | undefined;
|
|
55
66
|
/** Number of removed articles in diff result */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/cli/suggestions/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAC5D,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,8BAA8B,CAAC;AAEvE;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,gCAAgC;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,4CAA4C;IAC5C,WAAW,EAAE,MAAM,CAAC;CACrB;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,qCAAqC;IACrC,IAAI,EAAE,UAAU,EAAE,CAAC;IACnB,oCAAoC;IACpC,OAAO,EAAE,UAAU,EAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/cli/suggestions/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAC5D,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,8BAA8B,CAAC;AAEvE;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,gCAAgC;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,4CAA4C;IAC5C,WAAW,EAAE,MAAM,CAAC;CACrB;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,qCAAqC;IACrC,IAAI,EAAE,UAAU,EAAE,CAAC;IACnB,oCAAoC;IACpC,OAAO,EAAE,UAAU,EAAE,CAAC;IACtB,6CAA6C;IAC7C,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,gEAAgE;IAChE,EAAE,CAAC,EAAE;QACH,qEAAqE;QACrE,KAAK,EAAE,MAAM,CAAC;QACd,oBAAoB;QACpB,KAAK,EAAE,UAAU,EAAE,CAAC;KACrB,CAAC;CACH;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,oCAAoC;IACpC,OAAO,EAAE,MAAM,CAAC;IAChB,iCAAiC;IACjC,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC/B,qBAAqB;IACrB,aAAa,CAAC,EAAE,aAAa,GAAG,SAAS,CAAC;IAC1C,iDAAiD;IACjD,YAAY,CAAC,EAAE,kBAAkB,GAAG,SAAS,CAAC;IAC9C,kCAAkC;IAClC,YAAY,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAClC,kCAAkC;IAClC,UAAU,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IACjC,sBAAsB;IACtB,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC/B,2BAA2B;IAC3B,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC,oDAAoD;IACpD,cAAc,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACpC,yCAAyC;IACzC,aAAa,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACnC,4BAA4B;IAC5B,YAAY,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAClC,6BAA6B;IAC7B,aAAa,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACnC,wCAAwC;IACxC,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAChC,wDAAwD;IACxD,iBAAiB,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IACxC,gDAAgD;IAChD,aAAa,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IACpC,8CAA8C;IAC9C,cAAc,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACpC,gDAAgD;IAChD,gBAAgB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACtC,oCAAoC;IACpC,cAAc,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACrC;AAED;;GAEG;AACH,MAAM,MAAM,cAAc,GAAG,CAAC,GAAG,EAAE,iBAAiB,KAAK,gBAAgB,GAAG,IAAI,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { parseQueryFile, parseQueryString } from "./query/parser.js";
|
|
2
2
|
import { ValidationError, fieldTypeSchema, filtersSchema, formatValidationErrors, overrideBlockSchema, queryBlockSchema, queryFileSchema, termBlockSchema, validateQueryFile } from "./query/validator.js";
|
|
3
|
+
import { MeSHLookupClient } from "./query/mesh-lookup.js";
|
|
4
|
+
import { extractControlledVocabTerms, validateControlledVocab } from "./query/vocab-validator.js";
|
|
3
5
|
import { loadConfig, saveConfig } from "./config/loader.js";
|
|
4
6
|
import { ConfigSchema } from "./config/schema.js";
|
|
5
7
|
import { DEFAULT_CONFIG, getDefaultConfig } from "./config/defaults.js";
|
|
@@ -14,6 +16,7 @@ export {
|
|
|
14
16
|
BaseProvider,
|
|
15
17
|
ConfigSchema,
|
|
16
18
|
DEFAULT_CONFIG,
|
|
19
|
+
MeSHLookupClient,
|
|
17
20
|
MockProvider,
|
|
18
21
|
ProviderRegistry,
|
|
19
22
|
RateLimiter,
|
|
@@ -23,6 +26,7 @@ export {
|
|
|
23
26
|
createProviderRegistry,
|
|
24
27
|
createSession,
|
|
25
28
|
deserializeState,
|
|
29
|
+
extractControlledVocabTerms,
|
|
26
30
|
fieldTypeSchema,
|
|
27
31
|
filtersSchema,
|
|
28
32
|
formatValidationErrors,
|
|
@@ -49,6 +53,7 @@ export {
|
|
|
49
53
|
termBlockSchema,
|
|
50
54
|
updateDatabaseStatus,
|
|
51
55
|
updateSessionStatus,
|
|
56
|
+
validateControlledVocab,
|
|
52
57
|
validateQueryFile
|
|
53
58
|
};
|
|
54
59
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"translator.d.ts","sourceRoot":"","sources":["../../../src/providers/arxiv/translator.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAc,SAAS,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AACtF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"translator.d.ts","sourceRoot":"","sources":["../../../src/providers/arxiv/translator.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAc,SAAS,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AACtF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAexD;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,SAAS,GAAG,MAAM,GAAG,IAAI,CAEpE;AASD;;;GAGG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,QAAQ,EAAE,QAAQ,GAAG,MAAM,CAY7F;AAqID;;GAEG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,QAAQ,GAAG,eAAe,CAiE7D"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { collectUnsupportedVocabWarnings } from "../base/warnings.js";
|
|
1
2
|
const FIELD_PREFIXES = {
|
|
2
3
|
title: "ti:",
|
|
3
4
|
abstract: "abs:",
|
|
@@ -24,7 +25,7 @@ function translateTerms(prefix, keywords, operator) {
|
|
|
24
25
|
}
|
|
25
26
|
function translateBlock(block) {
|
|
26
27
|
const { field, terms, operator } = block;
|
|
27
|
-
const keywords = terms.keywords;
|
|
28
|
+
const keywords = terms.keywords ?? [];
|
|
28
29
|
let query = "";
|
|
29
30
|
if (keywords.length > 0) {
|
|
30
31
|
if (field === "title_abstract") {
|
|
@@ -135,10 +136,12 @@ function translateQuery(ast) {
|
|
|
135
136
|
native = notClause;
|
|
136
137
|
}
|
|
137
138
|
}
|
|
139
|
+
const warnings = collectUnsupportedVocabWarnings(ast.blocks, "arXiv", /* @__PURE__ */ new Set());
|
|
138
140
|
return {
|
|
139
141
|
native,
|
|
140
142
|
originalAst: ast,
|
|
141
|
-
provider: "arxiv"
|
|
143
|
+
provider: "arxiv",
|
|
144
|
+
...warnings.length > 0 ? { warnings } : {}
|
|
142
145
|
};
|
|
143
146
|
}
|
|
144
147
|
export {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"translator.js","sources":["../../../src/providers/arxiv/translator.ts"],"sourcesContent":["/**\n * arXiv Query Translator\n *\n * Translates QueryAST to arXiv-native query syntax.\n *\n * Field mappings:\n * - title → ti:\n * - abstract → abs:\n * - title_abstract → (ti: OR abs:) expansion\n * - author → au:\n * - all → all:\n * - keyword → not supported\n *\n * Boolean operators: AND, OR, ANDNOT (not standard NOT)\n * Date filter: submittedDate:[YYYYMMDDHHmm TO YYYYMMDDHHmm]\n * Category filter: cat:cs.AI\n */\n\nimport type { QueryAST, QueryBlock, FieldType, Operator } from '../../query/types.js';\nimport type { TranslatedQuery } from '../base/types.js';\n\n/**\n * Field prefix mappings for arXiv API.\n */\nconst FIELD_PREFIXES: Partial<Record<FieldType, string>> = {\n title: 'ti:',\n abstract: 'abs:',\n author: 'au:',\n all: 'all:',\n // title_abstract requires expansion, not a direct prefix\n // keyword is not supported by arXiv\n};\n\n/**\n * Get the arXiv field prefix for a DSL field type.\n * Returns null if the field requires special handling or is unsupported.\n */\nexport function translateFieldPrefix(field: FieldType): string | null {\n return FIELD_PREFIXES[field] ?? null;\n}\n\n/**\n * Wrap a term in quotes if it contains spaces (phrase search).\n */\nfunction quoteIfNeeded(term: string): string {\n return term.includes(' ') ? `\"${term}\"` : term;\n}\n\n/**\n * Translate terms with a field prefix.\n * Handles quoting of multi-word phrases.\n */\nexport function translateTerms(prefix: string, keywords: string[], operator: Operator): string {\n if (keywords.length === 0) {\n return '';\n }\n\n const translatedTerms = keywords.map((keyword) => `${prefix}${quoteIfNeeded(keyword)}`);\n\n if (translatedTerms.length === 1) {\n return translatedTerms[0]!;\n }\n\n return `(${translatedTerms.join(` ${operator} `)})`;\n}\n\n/**\n * Translate a single query block to arXiv syntax.\n * Returns an object with the main query part and optional ANDNOT clause.\n */\nfunction translateBlock(block: QueryBlock): { query: string; notClause: string | null } {\n const { field, terms, operator } = block;\n\n // arXiv only uses keywords; ignore mesh and emtree\n const keywords = terms.keywords;\n\n let query = '';\n\n if (keywords.length > 0) {\n // Handle title_abstract expansion\n if (field === 'title_abstract') {\n query = translateTitleAbstract(keywords, operator);\n } else {\n // Get field prefix\n const prefix = translateFieldPrefix(field);\n if (prefix !== null) {\n query = translateTerms(prefix, keywords, operator);\n }\n }\n }\n\n // Translate exclude terms\n const notClause = terms.exclude\n ? translateExcludeTerms(terms.exclude, field)\n : null;\n\n return { query, notClause };\n}\n\n/**\n * Expand title_abstract to (ti: OR abs:) for each keyword.\n */\nfunction translateTitleAbstract(keywords: string[], operator: Operator): string {\n if (keywords.length === 0) {\n return '';\n }\n\n // Each keyword expands to (ti:keyword OR abs:keyword)\n const expandedTerms = keywords.map((keyword) => {\n const quoted = quoteIfNeeded(keyword);\n return `(ti:${quoted} OR abs:${quoted})`;\n });\n\n if (expandedTerms.length === 1) {\n return expandedTerms[0]!;\n }\n\n return `(${expandedTerms.join(` ${operator} `)})`;\n}\n\n/**\n * Translate exclude terms to ANDNOT clause.\n * arXiv uses ANDNOT instead of NOT.\n */\nfunction translateExcludeTerms(\n exclude: string[],\n field: FieldType\n): string | null {\n if (exclude.length === 0) {\n return null;\n }\n\n // Handle title_abstract expansion\n if (field === 'title_abstract') {\n const expandedTerms = exclude.map((term) => {\n const quoted = quoteIfNeeded(term);\n return `(ti:${quoted} OR abs:${quoted})`;\n });\n if (expandedTerms.length === 1) {\n return `ANDNOT (${expandedTerms[0]})`;\n }\n return `ANDNOT (${expandedTerms.join(' OR ')})`;\n }\n\n // Get field prefix\n const prefix = translateFieldPrefix(field);\n if (prefix === null) {\n // Unsupported field\n return null;\n }\n\n const translatedTerms = exclude.map((term) => `${prefix}${quoteIfNeeded(term)}`);\n\n if (translatedTerms.length === 1) {\n return `ANDNOT ${translatedTerms[0]}`;\n }\n return `ANDNOT (${translatedTerms.join(' OR ')})`;\n}\n\n/**\n * Translate date filter to arXiv submittedDate range.\n * Format: submittedDate:[YYYYMMDDHHmm TO YYYYMMDDHHmm]\n * Note: arXiv API does not support wildcards (*), so we use concrete dates:\n * - Start: 1991 (arXiv's founding year)\n * - End: current year + 1 (to include all future submissions)\n */\nfunction translateDateFilter(yearFrom?: number, yearTo?: number): string {\n if (yearFrom === undefined && yearTo === undefined) {\n return '';\n }\n\n // arXiv was founded in 1991, use as default start\n const fromDate = yearFrom !== undefined ? `${yearFrom}01010000` : '199101010000';\n // Use next year as default end to include all current submissions\n const defaultEndYear = new Date().getFullYear() + 1;\n const toDate = yearTo !== undefined ? `${yearTo}12312359` : `${defaultEndYear}12312359`;\n\n return `submittedDate:[${fromDate} TO ${toDate}]`;\n}\n\n/**\n * Translate arXiv category filter.\n */\nfunction translateCategories(categories: string[]): string {\n if (categories.length === 0) {\n return '';\n }\n\n const catTerms = categories.map((cat) => `cat:${cat}`);\n\n if (catTerms.length === 1) {\n return catTerms[0]!;\n }\n\n return `(${catTerms.join(' OR ')})`;\n}\n\n/**\n * Translate a complete QueryAST to arXiv-native syntax.\n */\nexport function translateQuery(ast: QueryAST): TranslatedQuery {\n const parts: string[] = [];\n const notClauses: string[] = [];\n\n // Translate all blocks (AND'd together)\n const blockResults = ast.blocks.map(translateBlock);\n const blockParts = blockResults.map((r) => r.query).filter((part) => part !== '');\n const blockNotClauses = blockResults\n .map((r) => r.notClause)\n .filter((s): s is string => s !== null);\n\n notClauses.push(...blockNotClauses);\n\n if (blockParts.length > 0) {\n if (blockParts.length === 1) {\n parts.push(blockParts[0]!);\n } else {\n parts.push(blockParts.map((part) => `(${part})`).join(' AND '));\n }\n }\n\n // Add date filter if present\n const dateFilter = translateDateFilter(ast.filters.yearFrom, ast.filters.yearTo);\n if (dateFilter) {\n parts.push(`(${dateFilter})`);\n }\n\n // Add category filter from arXiv overrides\n const arxivOverrides = ast.overrides.arxiv;\n if (arxivOverrides?.categories && arxivOverrides.categories.length > 0) {\n const categoryFilter = translateCategories(arxivOverrides.categories);\n if (categoryFilter) {\n parts.push(`(${categoryFilter})`);\n }\n }\n\n // Combine all parts with AND\n let native: string;\n if (parts.length === 0) {\n native = '';\n } else if (parts.length === 1) {\n native = parts[0]!;\n } else {\n native = parts.join(' AND ');\n }\n\n // Append ANDNOT clauses\n for (const notClause of notClauses) {\n if (native) {\n native = `${native} ${notClause}`;\n } else {\n native = notClause;\n }\n }\n\n return {\n native,\n originalAst: ast,\n provider: 'arxiv',\n };\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"translator.js","sources":["../../../src/providers/arxiv/translator.ts"],"sourcesContent":["/**\n * arXiv Query Translator\n *\n * Translates QueryAST to arXiv-native query syntax.\n *\n * Field mappings:\n * - title → ti:\n * - abstract → abs:\n * - title_abstract → (ti: OR abs:) expansion\n * - author → au:\n * - all → all:\n * - keyword → not supported\n *\n * Boolean operators: AND, OR, ANDNOT (not standard NOT)\n * Date filter: submittedDate:[YYYYMMDDHHmm TO YYYYMMDDHHmm]\n * Category filter: cat:cs.AI\n */\n\nimport type { QueryAST, QueryBlock, FieldType, Operator } from '../../query/types.js';\nimport type { TranslatedQuery } from '../base/types.js';\nimport { collectUnsupportedVocabWarnings } from '../base/warnings.js';\n\n/**\n * Field prefix mappings for arXiv API.\n */\nconst FIELD_PREFIXES: Partial<Record<FieldType, string>> = {\n title: 'ti:',\n abstract: 'abs:',\n author: 'au:',\n all: 'all:',\n // title_abstract requires expansion, not a direct prefix\n // keyword is not supported by arXiv\n};\n\n/**\n * Get the arXiv field prefix for a DSL field type.\n * Returns null if the field requires special handling or is unsupported.\n */\nexport function translateFieldPrefix(field: FieldType): string | null {\n return FIELD_PREFIXES[field] ?? null;\n}\n\n/**\n * Wrap a term in quotes if it contains spaces (phrase search).\n */\nfunction quoteIfNeeded(term: string): string {\n return term.includes(' ') ? `\"${term}\"` : term;\n}\n\n/**\n * Translate terms with a field prefix.\n * Handles quoting of multi-word phrases.\n */\nexport function translateTerms(prefix: string, keywords: string[], operator: Operator): string {\n if (keywords.length === 0) {\n return '';\n }\n\n const translatedTerms = keywords.map((keyword) => `${prefix}${quoteIfNeeded(keyword)}`);\n\n if (translatedTerms.length === 1) {\n return translatedTerms[0]!;\n }\n\n return `(${translatedTerms.join(` ${operator} `)})`;\n}\n\n/**\n * Translate a single query block to arXiv syntax.\n * Returns an object with the main query part and optional ANDNOT clause.\n */\nfunction translateBlock(block: QueryBlock): { query: string; notClause: string | null } {\n const { field, terms, operator } = block;\n\n // arXiv only uses keywords; ignore mesh and emtree\n const keywords = terms.keywords ?? [];\n\n let query = '';\n\n if (keywords.length > 0) {\n // Handle title_abstract expansion\n if (field === 'title_abstract') {\n query = translateTitleAbstract(keywords, operator);\n } else {\n // Get field prefix\n const prefix = translateFieldPrefix(field);\n if (prefix !== null) {\n query = translateTerms(prefix, keywords, operator);\n }\n }\n }\n\n // Translate exclude terms\n const notClause = terms.exclude\n ? translateExcludeTerms(terms.exclude, field)\n : null;\n\n return { query, notClause };\n}\n\n/**\n * Expand title_abstract to (ti: OR abs:) for each keyword.\n */\nfunction translateTitleAbstract(keywords: string[], operator: Operator): string {\n if (keywords.length === 0) {\n return '';\n }\n\n // Each keyword expands to (ti:keyword OR abs:keyword)\n const expandedTerms = keywords.map((keyword) => {\n const quoted = quoteIfNeeded(keyword);\n return `(ti:${quoted} OR abs:${quoted})`;\n });\n\n if (expandedTerms.length === 1) {\n return expandedTerms[0]!;\n }\n\n return `(${expandedTerms.join(` ${operator} `)})`;\n}\n\n/**\n * Translate exclude terms to ANDNOT clause.\n * arXiv uses ANDNOT instead of NOT.\n */\nfunction translateExcludeTerms(\n exclude: string[],\n field: FieldType\n): string | null {\n if (exclude.length === 0) {\n return null;\n }\n\n // Handle title_abstract expansion\n if (field === 'title_abstract') {\n const expandedTerms = exclude.map((term) => {\n const quoted = quoteIfNeeded(term);\n return `(ti:${quoted} OR abs:${quoted})`;\n });\n if (expandedTerms.length === 1) {\n return `ANDNOT (${expandedTerms[0]})`;\n }\n return `ANDNOT (${expandedTerms.join(' OR ')})`;\n }\n\n // Get field prefix\n const prefix = translateFieldPrefix(field);\n if (prefix === null) {\n // Unsupported field\n return null;\n }\n\n const translatedTerms = exclude.map((term) => `${prefix}${quoteIfNeeded(term)}`);\n\n if (translatedTerms.length === 1) {\n return `ANDNOT ${translatedTerms[0]}`;\n }\n return `ANDNOT (${translatedTerms.join(' OR ')})`;\n}\n\n/**\n * Translate date filter to arXiv submittedDate range.\n * Format: submittedDate:[YYYYMMDDHHmm TO YYYYMMDDHHmm]\n * Note: arXiv API does not support wildcards (*), so we use concrete dates:\n * - Start: 1991 (arXiv's founding year)\n * - End: current year + 1 (to include all future submissions)\n */\nfunction translateDateFilter(yearFrom?: number, yearTo?: number): string {\n if (yearFrom === undefined && yearTo === undefined) {\n return '';\n }\n\n // arXiv was founded in 1991, use as default start\n const fromDate = yearFrom !== undefined ? `${yearFrom}01010000` : '199101010000';\n // Use next year as default end to include all current submissions\n const defaultEndYear = new Date().getFullYear() + 1;\n const toDate = yearTo !== undefined ? `${yearTo}12312359` : `${defaultEndYear}12312359`;\n\n return `submittedDate:[${fromDate} TO ${toDate}]`;\n}\n\n/**\n * Translate arXiv category filter.\n */\nfunction translateCategories(categories: string[]): string {\n if (categories.length === 0) {\n return '';\n }\n\n const catTerms = categories.map((cat) => `cat:${cat}`);\n\n if (catTerms.length === 1) {\n return catTerms[0]!;\n }\n\n return `(${catTerms.join(' OR ')})`;\n}\n\n/**\n * Translate a complete QueryAST to arXiv-native syntax.\n */\nexport function translateQuery(ast: QueryAST): TranslatedQuery {\n const parts: string[] = [];\n const notClauses: string[] = [];\n\n // Translate all blocks (AND'd together)\n const blockResults = ast.blocks.map(translateBlock);\n const blockParts = blockResults.map((r) => r.query).filter((part) => part !== '');\n const blockNotClauses = blockResults\n .map((r) => r.notClause)\n .filter((s): s is string => s !== null);\n\n notClauses.push(...blockNotClauses);\n\n if (blockParts.length > 0) {\n if (blockParts.length === 1) {\n parts.push(blockParts[0]!);\n } else {\n parts.push(blockParts.map((part) => `(${part})`).join(' AND '));\n }\n }\n\n // Add date filter if present\n const dateFilter = translateDateFilter(ast.filters.yearFrom, ast.filters.yearTo);\n if (dateFilter) {\n parts.push(`(${dateFilter})`);\n }\n\n // Add category filter from arXiv overrides\n const arxivOverrides = ast.overrides.arxiv;\n if (arxivOverrides?.categories && arxivOverrides.categories.length > 0) {\n const categoryFilter = translateCategories(arxivOverrides.categories);\n if (categoryFilter) {\n parts.push(`(${categoryFilter})`);\n }\n }\n\n // Combine all parts with AND\n let native: string;\n if (parts.length === 0) {\n native = '';\n } else if (parts.length === 1) {\n native = parts[0]!;\n } else {\n native = parts.join(' AND ');\n }\n\n // Append ANDNOT clauses\n for (const notClause of notClauses) {\n if (native) {\n native = `${native} ${notClause}`;\n } else {\n native = notClause;\n }\n }\n\n // Collect warnings for unsupported controlled vocabulary\n // arXiv does not support any controlled vocabulary\n const warnings = collectUnsupportedVocabWarnings(ast.blocks, 'arXiv', new Set());\n\n return {\n native,\n originalAst: ast,\n provider: 'arxiv',\n ...(warnings.length > 0 ? { warnings } : {}),\n };\n}\n"],"names":[],"mappings":";AAyBA,MAAM,iBAAqD;AAAA,EACzD,OAAO;AAAA,EACP,UAAU;AAAA,EACV,QAAQ;AAAA,EACR,KAAK;AAAA;AAAA;AAGP;AAMO,SAAS,qBAAqB,OAAiC;AACpE,SAAO,eAAe,KAAK,KAAK;AAClC;AAKA,SAAS,cAAc,MAAsB;AAC3C,SAAO,KAAK,SAAS,GAAG,IAAI,IAAI,IAAI,MAAM;AAC5C;AAMO,SAAS,eAAe,QAAgB,UAAoB,UAA4B;AAC7F,MAAI,SAAS,WAAW,GAAG;AACzB,WAAO;AAAA,EACT;AAEA,QAAM,kBAAkB,SAAS,IAAI,CAAC,YAAY,GAAG,MAAM,GAAG,cAAc,OAAO,CAAC,EAAE;AAEtF,MAAI,gBAAgB,WAAW,GAAG;AAChC,WAAO,gBAAgB,CAAC;AAAA,EAC1B;AAEA,SAAO,IAAI,gBAAgB,KAAK,IAAI,QAAQ,GAAG,CAAC;AAClD;AAMA,SAAS,eAAe,OAAgE;AACtF,QAAM,EAAE,OAAO,OAAO,SAAA,IAAa;AAGnC,QAAM,WAAW,MAAM,YAAY,CAAA;AAEnC,MAAI,QAAQ;AAEZ,MAAI,SAAS,SAAS,GAAG;AAEvB,QAAI,UAAU,kBAAkB;AAC9B,cAAQ,uBAAuB,UAAU,QAAQ;AAAA,IACnD,OAAO;AAEL,YAAM,SAAS,qBAAqB,KAAK;AACzC,UAAI,WAAW,MAAM;AACnB,gBAAQ,eAAe,QAAQ,UAAU,QAAQ;AAAA,MACnD;AAAA,IACF;AAAA,EACF;AAGA,QAAM,YAAY,MAAM,UACpB,sBAAsB,MAAM,SAAS,KAAK,IAC1C;AAEJ,SAAO,EAAE,OAAO,UAAA;AAClB;AAKA,SAAS,uBAAuB,UAAoB,UAA4B;AAC9E,MAAI,SAAS,WAAW,GAAG;AACzB,WAAO;AAAA,EACT;AAGA,QAAM,gBAAgB,SAAS,IAAI,CAAC,YAAY;AAC9C,UAAM,SAAS,cAAc,OAAO;AACpC,WAAO,OAAO,MAAM,WAAW,MAAM;AAAA,EACvC,CAAC;AAED,MAAI,cAAc,WAAW,GAAG;AAC9B,WAAO,cAAc,CAAC;AAAA,EACxB;AAEA,SAAO,IAAI,cAAc,KAAK,IAAI,QAAQ,GAAG,CAAC;AAChD;AAMA,SAAS,sBACP,SACA,OACe;AACf,MAAI,QAAQ,WAAW,GAAG;AACxB,WAAO;AAAA,EACT;AAGA,MAAI,UAAU,kBAAkB;AAC9B,UAAM,gBAAgB,QAAQ,IAAI,CAAC,SAAS;AAC1C,YAAM,SAAS,cAAc,IAAI;AACjC,aAAO,OAAO,MAAM,WAAW,MAAM;AAAA,IACvC,CAAC;AACD,QAAI,cAAc,WAAW,GAAG;AAC9B,aAAO,WAAW,cAAc,CAAC,CAAC;AAAA,IACpC;AACA,WAAO,WAAW,cAAc,KAAK,MAAM,CAAC;AAAA,EAC9C;AAGA,QAAM,SAAS,qBAAqB,KAAK;AACzC,MAAI,WAAW,MAAM;AAEnB,WAAO;AAAA,EACT;AAEA,QAAM,kBAAkB,QAAQ,IAAI,CAAC,SAAS,GAAG,MAAM,GAAG,cAAc,IAAI,CAAC,EAAE;AAE/E,MAAI,gBAAgB,WAAW,GAAG;AAChC,WAAO,UAAU,gBAAgB,CAAC,CAAC;AAAA,EACrC;AACA,SAAO,WAAW,gBAAgB,KAAK,MAAM,CAAC;AAChD;AASA,SAAS,oBAAoB,UAAmB,QAAyB;AACvE,MAAI,aAAa,UAAa,WAAW,QAAW;AAClD,WAAO;AAAA,EACT;AAGA,QAAM,WAAW,aAAa,SAAY,GAAG,QAAQ,aAAa;AAElE,QAAM,kBAAiB,oBAAI,KAAA,GAAO,gBAAgB;AAClD,QAAM,SAAS,WAAW,SAAY,GAAG,MAAM,aAAa,GAAG,cAAc;AAE7E,SAAO,kBAAkB,QAAQ,OAAO,MAAM;AAChD;AAKA,SAAS,oBAAoB,YAA8B;AACzD,MAAI,WAAW,WAAW,GAAG;AAC3B,WAAO;AAAA,EACT;AAEA,QAAM,WAAW,WAAW,IAAI,CAAC,QAAQ,OAAO,GAAG,EAAE;AAErD,MAAI,SAAS,WAAW,GAAG;AACzB,WAAO,SAAS,CAAC;AAAA,EACnB;AAEA,SAAO,IAAI,SAAS,KAAK,MAAM,CAAC;AAClC;AAKO,SAAS,eAAe,KAAgC;AAC7D,QAAM,QAAkB,CAAA;AACxB,QAAM,aAAuB,CAAA;AAG7B,QAAM,eAAe,IAAI,OAAO,IAAI,cAAc;AAClD,QAAM,aAAa,aAAa,IAAI,CAAC,MAAM,EAAE,KAAK,EAAE,OAAO,CAAC,SAAS,SAAS,EAAE;AAChF,QAAM,kBAAkB,aACrB,IAAI,CAAC,MAAM,EAAE,SAAS,EACtB,OAAO,CAAC,MAAmB,MAAM,IAAI;AAExC,aAAW,KAAK,GAAG,eAAe;AAElC,MAAI,WAAW,SAAS,GAAG;AACzB,QAAI,WAAW,WAAW,GAAG;AAC3B,YAAM,KAAK,WAAW,CAAC,CAAE;AAAA,IAC3B,OAAO;AACL,YAAM,KAAK,WAAW,IAAI,CAAC,SAAS,IAAI,IAAI,GAAG,EAAE,KAAK,OAAO,CAAC;AAAA,IAChE;AAAA,EACF;AAGA,QAAM,aAAa,oBAAoB,IAAI,QAAQ,UAAU,IAAI,QAAQ,MAAM;AAC/E,MAAI,YAAY;AACd,UAAM,KAAK,IAAI,UAAU,GAAG;AAAA,EAC9B;AAGA,QAAM,iBAAiB,IAAI,UAAU;AACrC,MAAI,gBAAgB,cAAc,eAAe,WAAW,SAAS,GAAG;AACtE,UAAM,iBAAiB,oBAAoB,eAAe,UAAU;AACpE,QAAI,gBAAgB;AAClB,YAAM,KAAK,IAAI,cAAc,GAAG;AAAA,IAClC;AAAA,EACF;AAGA,MAAI;AACJ,MAAI,MAAM,WAAW,GAAG;AACtB,aAAS;AAAA,EACX,WAAW,MAAM,WAAW,GAAG;AAC7B,aAAS,MAAM,CAAC;AAAA,EAClB,OAAO;AACL,aAAS,MAAM,KAAK,OAAO;AAAA,EAC7B;AAGA,aAAW,aAAa,YAAY;AAClC,QAAI,QAAQ;AACV,eAAS,GAAG,MAAM,IAAI,SAAS;AAAA,IACjC,OAAO;AACL,eAAS;AAAA,IACX;AAAA,EACF;AAIA,QAAM,WAAW,gCAAgC,IAAI,QAAQ,SAAS,oBAAI,KAAK;AAE/E,SAAO;AAAA,IACL;AAAA,IACA,aAAa;AAAA,IACb,UAAU;AAAA,IACV,GAAI,SAAS,SAAS,IAAI,EAAE,SAAA,IAAa,CAAA;AAAA,EAAC;AAE9C;"}
|
|
@@ -49,6 +49,8 @@ export interface TranslatedQuery {
|
|
|
49
49
|
originalAst?: QueryAST;
|
|
50
50
|
/** Provider that produced this translation */
|
|
51
51
|
provider: ProviderName;
|
|
52
|
+
/** Warnings about unsupported controlled vocabulary */
|
|
53
|
+
warnings?: string[];
|
|
52
54
|
}
|
|
53
55
|
/**
|
|
54
56
|
* Search options for controlling query execution.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/providers/base/types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAGrD,YAAY,EAAE,QAAQ,EAAE,CAAC;AAEzB;;GAEG;AACH,MAAM,MAAM,YAAY,GACpB,QAAQ,GACR,MAAM,GACN,OAAO,GACP,QAAQ,GACR,KAAK,GACL,QAAQ,CAAC;AAEb;;GAEG;AACH,MAAM,WAAW,MAAM;IACrB,2BAA2B;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,iBAAiB;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,gCAAgC;IAChC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,uBAAuB;IACvB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;GAGG;AACH,MAAM,WAAW,OAAO;IAEtB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAGhB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,MAAM,EAAE,YAAY,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IAGpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,mCAAmC;IACnC,MAAM,EAAE,MAAM,CAAC;IACf,iEAAiE;IACjE,WAAW,CAAC,EAAE,QAAQ,CAAC;IACvB,8CAA8C;IAC9C,QAAQ,EAAE,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/providers/base/types.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sources":["../../../src/providers/base/types.ts"],"sourcesContent":["/**\n * Provider base types for the search-hub CLI tool.\n * These types define the common interface for all database providers.\n */\n\nimport type { QueryAST } from '../../query/types.js';\n\n// Re-export QueryAST for convenience\nexport type { QueryAST };\n\n/**\n * Supported provider names.\n */\nexport type ProviderName =\n | 'pubmed'\n | 'eric'\n | 'arxiv'\n | 'scopus'\n | 'wos'\n | 'embase';\n\n/**\n * Author information.\n */\nexport interface Author {\n /** Last name (required) */\n family: string;\n /** First name */\n given?: string;\n /** Institutional affiliation */\n affiliation?: string;\n /** ORCID identifier */\n orcid?: string;\n}\n\n/**\n * Represents a single search result from any database.\n * At least one identifier (doi, pmid, arxivId, scopusId, ericId) is required.\n */\nexport interface Article {\n // Identifiers (at least one required)\n doi?: string;\n pmid?: string;\n arxivId?: string;\n scopusId?: string;\n ericId?: string;\n\n // Required fields\n title: string;\n authors: Author[];\n source: ProviderName;\n retrievedAt: string; // ISO 8601 format\n\n // Optional fields\n abstract?: string;\n publicationDate?: string;\n journal?: string;\n volume?: string;\n issue?: string;\n pages?: string;\n rawResponse?: unknown;\n}\n\n/**\n * Result of translating a query to database-native syntax.\n */\nexport interface TranslatedQuery {\n /** Database-native query string */\n native: string;\n /** Reference to original AST (optional for direct query mode) */\n originalAst?: QueryAST;\n /** Provider that produced this translation */\n provider: ProviderName;\n}\n\n/**\n * Search options for controlling query execution.\n */\nexport interface SearchOptions {\n /** Maximum number of results to retrieve */\n maxResults?: number;\n /** Number of results per page/request */\n pageSize?: number;\n /** Date range filter */\n dateRange?: {\n start: string;\n end: string;\n };\n /** AbortSignal for cancellation */\n signal?: AbortSignal;\n}\n\n/**\n * Result of a connection test.\n */\nexport interface ConnectionTestResult {\n /** Whether the connection succeeded */\n ok: boolean;\n /** Error message if the connection failed */\n error?: string;\n}\n\n/**\n * Core provider interface that all database providers must implement.\n */\nexport interface Provider {\n /** Provider name identifier */\n readonly name: ProviderName;\n\n /**\n * Execute search and return results as async iterable (streaming).\n */\n search(\n query: TranslatedQuery,\n options?: SearchOptions\n ): AsyncIterable<Article>;\n\n /**\n * Get total hit count for a query without downloading results.\n * Used for count-only mode during query refinement.\n */\n count(query: TranslatedQuery): Promise<number>;\n\n /**\n * Convert QueryAST to database-native syntax.\n */\n translateQuery(ast: QueryAST): TranslatedQuery;\n\n /**\n * Verify API access and credentials.\n * Returns { ok: true } on success, { ok: false, error: string } on failure.\n * Does not throw.\n */\n testConnection(): Promise<ConnectionTestResult>;\n}\n\n/**\n * Error codes used by providers.\n */\nexport type ProviderErrorCode =\n | 'PROVIDER_NOT_AVAILABLE'\n | 'API_KEY_MISSING'\n | 'API_KEY_INVALID'\n | 'ACCESS_DENIED'\n | 'RATE_LIMIT_EXCEEDED'\n | 'NETWORK_ERROR'\n | 'PARSE_ERROR'\n | 'QUERY_ERROR'\n | 'SERVER_ERROR'\n | 'TIMEOUT';\n\n/**\n * Base error type for provider errors.\n */\nexport interface ProviderError {\n code: ProviderErrorCode;\n message: string;\n provider: ProviderName;\n retryable: boolean;\n cause?: unknown;\n}\n\n/**\n * Rate limit exceeded error with retry information.\n */\nexport interface RateLimitError extends ProviderError {\n code: 'RATE_LIMIT_EXCEEDED';\n /** Time to wait before retrying (in milliseconds) */\n retryAfter?: number;\n}\n\n/**\n * Authentication/authorization error.\n */\nexport interface AuthError extends ProviderError {\n code: 'API_KEY_MISSING' | 'API_KEY_INVALID' | 'ACCESS_DENIED';\n}\n\n/**\n * Create a provider error.\n */\nexport function createProviderError(\n code: ProviderErrorCode,\n message: string,\n provider: ProviderName,\n options?: { retryable?: boolean; cause?: unknown }\n): ProviderError {\n return {\n code,\n message,\n provider,\n retryable: options?.retryable ?? false,\n cause: options?.cause,\n };\n}\n\n/**\n * Check if an error is a provider error.\n */\nexport function isProviderError(error: unknown): error is ProviderError {\n if (typeof error !== 'object' || error === null) {\n return false;\n }\n const e = error as Record<string, unknown>;\n return (\n typeof e['code'] === 'string' &&\n typeof e['message'] === 'string' &&\n typeof e['provider'] === 'string' &&\n typeof e['retryable'] === 'boolean'\n );\n}\n\n/**\n * Check if an error is a rate limit error.\n */\nexport function isRateLimitError(error: unknown): error is RateLimitError {\n return isProviderError(error) && error.code === 'RATE_LIMIT_EXCEEDED';\n}\n\n/**\n * Check if an error is an auth error.\n */\nexport function isAuthError(error: unknown): error is AuthError {\n return (\n isProviderError(error) &&\n (error.code === 'API_KEY_MISSING' || error.code === 'API_KEY_INVALID' || error.code === 'ACCESS_DENIED')\n );\n}\n\n/**\n * Represents the current state of a search for session persistence.\n * Used to resume searches after interruption or application restart.\n */\nexport interface SearchState {\n /** Provider that produced this state */\n provider: ProviderName;\n /** The query being executed */\n query: TranslatedQuery;\n /** Total number of results available */\n totalResults: number;\n /** Number of results retrieved so far */\n retrievedCount: number;\n /** When the state was last updated */\n lastUpdated: Date;\n /** Provider-specific state (e.g., PubMed webenv/querykey, or offset for other providers) */\n providerState?: unknown;\n}\n\n/**\n * Result of validating a search state for resume.\n */\nexport interface SearchResumeResult {\n /** Whether the state is valid for resuming */\n valid: boolean;\n /** Reason if the state is invalid */\n reason?: string;\n}\n"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"types.js","sources":["../../../src/providers/base/types.ts"],"sourcesContent":["/**\n * Provider base types for the search-hub CLI tool.\n * These types define the common interface for all database providers.\n */\n\nimport type { QueryAST } from '../../query/types.js';\n\n// Re-export QueryAST for convenience\nexport type { QueryAST };\n\n/**\n * Supported provider names.\n */\nexport type ProviderName =\n | 'pubmed'\n | 'eric'\n | 'arxiv'\n | 'scopus'\n | 'wos'\n | 'embase';\n\n/**\n * Author information.\n */\nexport interface Author {\n /** Last name (required) */\n family: string;\n /** First name */\n given?: string;\n /** Institutional affiliation */\n affiliation?: string;\n /** ORCID identifier */\n orcid?: string;\n}\n\n/**\n * Represents a single search result from any database.\n * At least one identifier (doi, pmid, arxivId, scopusId, ericId) is required.\n */\nexport interface Article {\n // Identifiers (at least one required)\n doi?: string;\n pmid?: string;\n arxivId?: string;\n scopusId?: string;\n ericId?: string;\n\n // Required fields\n title: string;\n authors: Author[];\n source: ProviderName;\n retrievedAt: string; // ISO 8601 format\n\n // Optional fields\n abstract?: string;\n publicationDate?: string;\n journal?: string;\n volume?: string;\n issue?: string;\n pages?: string;\n rawResponse?: unknown;\n}\n\n/**\n * Result of translating a query to database-native syntax.\n */\nexport interface TranslatedQuery {\n /** Database-native query string */\n native: string;\n /** Reference to original AST (optional for direct query mode) */\n originalAst?: QueryAST;\n /** Provider that produced this translation */\n provider: ProviderName;\n /** Warnings about unsupported controlled vocabulary */\n warnings?: string[];\n}\n\n/**\n * Search options for controlling query execution.\n */\nexport interface SearchOptions {\n /** Maximum number of results to retrieve */\n maxResults?: number;\n /** Number of results per page/request */\n pageSize?: number;\n /** Date range filter */\n dateRange?: {\n start: string;\n end: string;\n };\n /** AbortSignal for cancellation */\n signal?: AbortSignal;\n}\n\n/**\n * Result of a connection test.\n */\nexport interface ConnectionTestResult {\n /** Whether the connection succeeded */\n ok: boolean;\n /** Error message if the connection failed */\n error?: string;\n}\n\n/**\n * Core provider interface that all database providers must implement.\n */\nexport interface Provider {\n /** Provider name identifier */\n readonly name: ProviderName;\n\n /**\n * Execute search and return results as async iterable (streaming).\n */\n search(\n query: TranslatedQuery,\n options?: SearchOptions\n ): AsyncIterable<Article>;\n\n /**\n * Get total hit count for a query without downloading results.\n * Used for count-only mode during query refinement.\n */\n count(query: TranslatedQuery): Promise<number>;\n\n /**\n * Convert QueryAST to database-native syntax.\n */\n translateQuery(ast: QueryAST): TranslatedQuery;\n\n /**\n * Verify API access and credentials.\n * Returns { ok: true } on success, { ok: false, error: string } on failure.\n * Does not throw.\n */\n testConnection(): Promise<ConnectionTestResult>;\n}\n\n/**\n * Error codes used by providers.\n */\nexport type ProviderErrorCode =\n | 'PROVIDER_NOT_AVAILABLE'\n | 'API_KEY_MISSING'\n | 'API_KEY_INVALID'\n | 'ACCESS_DENIED'\n | 'RATE_LIMIT_EXCEEDED'\n | 'NETWORK_ERROR'\n | 'PARSE_ERROR'\n | 'QUERY_ERROR'\n | 'SERVER_ERROR'\n | 'TIMEOUT';\n\n/**\n * Base error type for provider errors.\n */\nexport interface ProviderError {\n code: ProviderErrorCode;\n message: string;\n provider: ProviderName;\n retryable: boolean;\n cause?: unknown;\n}\n\n/**\n * Rate limit exceeded error with retry information.\n */\nexport interface RateLimitError extends ProviderError {\n code: 'RATE_LIMIT_EXCEEDED';\n /** Time to wait before retrying (in milliseconds) */\n retryAfter?: number;\n}\n\n/**\n * Authentication/authorization error.\n */\nexport interface AuthError extends ProviderError {\n code: 'API_KEY_MISSING' | 'API_KEY_INVALID' | 'ACCESS_DENIED';\n}\n\n/**\n * Create a provider error.\n */\nexport function createProviderError(\n code: ProviderErrorCode,\n message: string,\n provider: ProviderName,\n options?: { retryable?: boolean; cause?: unknown }\n): ProviderError {\n return {\n code,\n message,\n provider,\n retryable: options?.retryable ?? false,\n cause: options?.cause,\n };\n}\n\n/**\n * Check if an error is a provider error.\n */\nexport function isProviderError(error: unknown): error is ProviderError {\n if (typeof error !== 'object' || error === null) {\n return false;\n }\n const e = error as Record<string, unknown>;\n return (\n typeof e['code'] === 'string' &&\n typeof e['message'] === 'string' &&\n typeof e['provider'] === 'string' &&\n typeof e['retryable'] === 'boolean'\n );\n}\n\n/**\n * Check if an error is a rate limit error.\n */\nexport function isRateLimitError(error: unknown): error is RateLimitError {\n return isProviderError(error) && error.code === 'RATE_LIMIT_EXCEEDED';\n}\n\n/**\n * Check if an error is an auth error.\n */\nexport function isAuthError(error: unknown): error is AuthError {\n return (\n isProviderError(error) &&\n (error.code === 'API_KEY_MISSING' || error.code === 'API_KEY_INVALID' || error.code === 'ACCESS_DENIED')\n );\n}\n\n/**\n * Represents the current state of a search for session persistence.\n * Used to resume searches after interruption or application restart.\n */\nexport interface SearchState {\n /** Provider that produced this state */\n provider: ProviderName;\n /** The query being executed */\n query: TranslatedQuery;\n /** Total number of results available */\n totalResults: number;\n /** Number of results retrieved so far */\n retrievedCount: number;\n /** When the state was last updated */\n lastUpdated: Date;\n /** Provider-specific state (e.g., PubMed webenv/querykey, or offset for other providers) */\n providerState?: unknown;\n}\n\n/**\n * Result of validating a search state for resume.\n */\nexport interface SearchResumeResult {\n /** Whether the state is valid for resuming */\n valid: boolean;\n /** Reason if the state is invalid */\n reason?: string;\n}\n"],"names":[],"mappings":"AAuLO,SAAS,oBACd,MACA,SACA,UACA,SACe;AACf,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA,WAAW,SAAS,aAAa;AAAA,IACjC,OAAO,SAAS;AAAA,EAAA;AAEpB;AAKO,SAAS,gBAAgB,OAAwC;AACtE,MAAI,OAAO,UAAU,YAAY,UAAU,MAAM;AAC/C,WAAO;AAAA,EACT;AACA,QAAM,IAAI;AACV,SACE,OAAO,EAAE,MAAM,MAAM,YACrB,OAAO,EAAE,SAAS,MAAM,YACxB,OAAO,EAAE,UAAU,MAAM,YACzB,OAAO,EAAE,WAAW,MAAM;AAE9B;AAKO,SAAS,iBAAiB,OAAyC;AACxE,SAAO,gBAAgB,KAAK,KAAK,MAAM,SAAS;AAClD;AAKO,SAAS,YAAY,OAAoC;AAC9D,SACE,gBAAgB,KAAK,MACpB,MAAM,SAAS,qBAAqB,MAAM,SAAS,qBAAqB,MAAM,SAAS;AAE5F;"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { QueryBlock } from '../../query/types';
|
|
2
|
+
/** Controlled vocabulary types that can appear in a TermBlock */
|
|
3
|
+
type VocabType = 'mesh' | 'emtree' | 'eric';
|
|
4
|
+
/**
|
|
5
|
+
* Collect warnings for unsupported controlled vocabulary in query blocks.
|
|
6
|
+
*
|
|
7
|
+
* @param blocks - Query blocks to check
|
|
8
|
+
* @param providerDisplayName - Display name of the provider (e.g., "arXiv", "Scopus")
|
|
9
|
+
* @param supportedVocab - Set of controlled vocabulary types this provider supports
|
|
10
|
+
* @returns Array of warning messages
|
|
11
|
+
*/
|
|
12
|
+
export declare function collectUnsupportedVocabWarnings(blocks: QueryBlock[], providerDisplayName: string, supportedVocab: ReadonlySet<VocabType>): string[];
|
|
13
|
+
export {};
|
|
14
|
+
//# sourceMappingURL=warnings.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"warnings.d.ts","sourceRoot":"","sources":["../../../src/providers/base/warnings.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAEpD,iEAAiE;AACjE,KAAK,SAAS,GAAG,MAAM,GAAG,QAAQ,GAAG,MAAM,CAAC;AAS5C;;;;;;;GAOG;AACH,wBAAgB,+BAA+B,CAC7C,MAAM,EAAE,UAAU,EAAE,EACpB,mBAAmB,EAAE,MAAM,EAC3B,cAAc,EAAE,WAAW,CAAC,SAAS,CAAC,GACrC,MAAM,EAAE,CAyBV"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
const VOCAB_DISPLAY_NAMES = {
|
|
2
|
+
mesh: "MeSH",
|
|
3
|
+
emtree: "Emtree",
|
|
4
|
+
eric: "ERIC descriptor"
|
|
5
|
+
};
|
|
6
|
+
function collectUnsupportedVocabWarnings(blocks, providerDisplayName, supportedVocab) {
|
|
7
|
+
const warnings = [];
|
|
8
|
+
const vocabTypes = ["mesh", "emtree", "eric"];
|
|
9
|
+
for (let i = 0; i < blocks.length; i++) {
|
|
10
|
+
const block = blocks[i];
|
|
11
|
+
const hasKeywords = block.terms.keywords && block.terms.keywords.length > 0;
|
|
12
|
+
for (const vocab of vocabTypes) {
|
|
13
|
+
if (!supportedVocab.has(vocab) && block.terms[vocab] && block.terms[vocab].length > 0) {
|
|
14
|
+
const displayName = VOCAB_DISPLAY_NAMES[vocab];
|
|
15
|
+
const blockNum = i + 1;
|
|
16
|
+
if (hasKeywords) {
|
|
17
|
+
warnings.push(
|
|
18
|
+
`${providerDisplayName}: ${displayName} terms in block ${blockNum} ignored (not supported) — keywords still searched`
|
|
19
|
+
);
|
|
20
|
+
} else {
|
|
21
|
+
warnings.push(
|
|
22
|
+
`${providerDisplayName}: block ${blockNum} skipped (contains only ${displayName} terms, not supported)`
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return warnings;
|
|
29
|
+
}
|
|
30
|
+
export {
|
|
31
|
+
collectUnsupportedVocabWarnings
|
|
32
|
+
};
|
|
33
|
+
//# sourceMappingURL=warnings.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"warnings.js","sources":["../../../src/providers/base/warnings.ts"],"sourcesContent":["/**\n * Unsupported controlled vocabulary warnings.\n *\n * Generates warnings when query blocks contain controlled vocabulary\n * terms that the target provider does not support.\n */\n\nimport type { QueryBlock } from '../../query/types';\n\n/** Controlled vocabulary types that can appear in a TermBlock */\ntype VocabType = 'mesh' | 'emtree' | 'eric';\n\n/** Human-readable names for controlled vocabulary types */\nconst VOCAB_DISPLAY_NAMES: Record<VocabType, string> = {\n mesh: 'MeSH',\n emtree: 'Emtree',\n eric: 'ERIC descriptor',\n};\n\n/**\n * Collect warnings for unsupported controlled vocabulary in query blocks.\n *\n * @param blocks - Query blocks to check\n * @param providerDisplayName - Display name of the provider (e.g., \"arXiv\", \"Scopus\")\n * @param supportedVocab - Set of controlled vocabulary types this provider supports\n * @returns Array of warning messages\n */\nexport function collectUnsupportedVocabWarnings(\n blocks: QueryBlock[],\n providerDisplayName: string,\n supportedVocab: ReadonlySet<VocabType>\n): string[] {\n const warnings: string[] = [];\n const vocabTypes: VocabType[] = ['mesh', 'emtree', 'eric'];\n\n for (let i = 0; i < blocks.length; i++) {\n const block = blocks[i]!;\n const hasKeywords = block.terms.keywords && block.terms.keywords.length > 0;\n for (const vocab of vocabTypes) {\n if (!supportedVocab.has(vocab) && block.terms[vocab] && block.terms[vocab].length > 0) {\n const displayName = VOCAB_DISPLAY_NAMES[vocab];\n const blockNum = i + 1;\n if (hasKeywords) {\n warnings.push(\n `${providerDisplayName}: ${displayName} terms in block ${blockNum} ignored (not supported) — keywords still searched`\n );\n } else {\n warnings.push(\n `${providerDisplayName}: block ${blockNum} skipped (contains only ${displayName} terms, not supported)`\n );\n }\n }\n }\n }\n\n return warnings;\n}\n"],"names":[],"mappings":"AAaA,MAAM,sBAAiD;AAAA,EACrD,MAAM;AAAA,EACN,QAAQ;AAAA,EACR,MAAM;AACR;AAUO,SAAS,gCACd,QACA,qBACA,gBACU;AACV,QAAM,WAAqB,CAAA;AAC3B,QAAM,aAA0B,CAAC,QAAQ,UAAU,MAAM;AAEzD,WAAS,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK;AACtC,UAAM,QAAQ,OAAO,CAAC;AACtB,UAAM,cAAc,MAAM,MAAM,YAAY,MAAM,MAAM,SAAS,SAAS;AAC1E,eAAW,SAAS,YAAY;AAC9B,UAAI,CAAC,eAAe,IAAI,KAAK,KAAK,MAAM,MAAM,KAAK,KAAK,MAAM,MAAM,KAAK,EAAE,SAAS,GAAG;AACrF,cAAM,cAAc,oBAAoB,KAAK;AAC7C,cAAM,WAAW,IAAI;AACrB,YAAI,aAAa;AACf,mBAAS;AAAA,YACP,GAAG,mBAAmB,KAAK,WAAW,mBAAmB,QAAQ;AAAA,UAAA;AAAA,QAErE,OAAO;AACL,mBAAS;AAAA,YACP,GAAG,mBAAmB,WAAW,QAAQ,2BAA2B,WAAW;AAAA,UAAA;AAAA,QAEnF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AACT;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"translator.d.ts","sourceRoot":"","sources":["../../../src/providers/eric/translator.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAuB,MAAM,mBAAmB,CAAC;AACvE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"translator.d.ts","sourceRoot":"","sources":["../../../src/providers/eric/translator.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAuB,MAAM,mBAAmB,CAAC;AACvE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AA8IrD;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,QAAQ,GAAG,eAAe,CA+ChE;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,QAAQ,GAAG,eAAe,CAE7D"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { collectUnsupportedVocabWarnings } from "../base/warnings.js";
|
|
1
2
|
const FIELD_PREFIXES = {
|
|
2
3
|
title: "title:",
|
|
3
4
|
abstract: "description:",
|
|
@@ -42,7 +43,7 @@ function translateEricDescriptors(descriptors) {
|
|
|
42
43
|
}
|
|
43
44
|
function translateBlock(block) {
|
|
44
45
|
const { field, terms, operator } = block;
|
|
45
|
-
const keywords = terms.keywords;
|
|
46
|
+
const keywords = terms.keywords ?? [];
|
|
46
47
|
const eric = terms.eric ?? [];
|
|
47
48
|
const allTerms = [];
|
|
48
49
|
if (keywords.length > 0) {
|
|
@@ -105,10 +106,12 @@ function translateQueryAST(ast) {
|
|
|
105
106
|
native = dateFilter;
|
|
106
107
|
}
|
|
107
108
|
}
|
|
109
|
+
const warnings = collectUnsupportedVocabWarnings(ast.blocks, "ERIC", /* @__PURE__ */ new Set(["eric"]));
|
|
108
110
|
return {
|
|
109
111
|
native,
|
|
110
112
|
originalAst: ast,
|
|
111
|
-
provider: "eric"
|
|
113
|
+
provider: "eric",
|
|
114
|
+
...warnings.length > 0 ? { warnings } : {}
|
|
112
115
|
};
|
|
113
116
|
}
|
|
114
117
|
function translateQuery(ast) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"translator.js","sources":["../../../src/providers/eric/translator.ts"],"sourcesContent":["/**\n * ERIC query translator.\n * Converts QueryAST to ERIC-native query syntax.\n */\n\nimport type { QueryAST, QueryBlock, Filters } from '../../query/types';\nimport type { TranslatedQuery } from '../base/types';\n\n/**\n * Field prefix mappings for ERIC.\n * Maps DSL field types to ERIC query syntax prefixes.\n */\nconst FIELD_PREFIXES: Record<string, string> = {\n title: 'title:',\n abstract: 'description:', // ERIC uses 'description' field for abstracts\n author: 'author:',\n keyword: 'subject:', // ERIC uses subject for descriptors\n all: '', // No prefix for all-field search\n};\n\n/**\n * Check if a term needs quoting (contains spaces or special characters).\n */\nfunction needsQuotes(term: string): boolean {\n return /\\s|[()[\\]{}:\"]/.test(term);\n}\n\n/**\n * Format a term with optional field prefix.\n * Quotes multi-word phrases automatically.\n */\nfunction formatTerm(term: string, prefix: string): string {\n const quoted = needsQuotes(term) ? `\"${term}\"` : term;\n return `${prefix}${quoted}`;\n}\n\n/**\n * Translate a single term for title_abstract field.\n * Expands to (title:term OR description:term).\n * Note: ERIC uses 'description' field for abstracts.\n */\nfunction translateTitleAbstractTerm(term: string): string {\n const quoted = needsQuotes(term) ? `\"${term}\"` : term;\n return `(title:${quoted} OR description:${quoted})`;\n}\n\n/**\n * Translate exclude terms to NOT clause.\n */\nfunction translateExcludeTerms(\n exclude: string[],\n field: QueryBlock['field']\n): string | null {\n if (exclude.length === 0) {\n return null;\n }\n\n // Handle title_abstract expansion\n if (field === 'title_abstract') {\n const expandedTerms = exclude.map(translateTitleAbstractTerm);\n if (expandedTerms.length === 1) {\n return `NOT ${expandedTerms[0]}`;\n }\n return `NOT (${expandedTerms.join(' OR ')})`;\n }\n\n // Standard field translation\n const prefix = FIELD_PREFIXES[field] ?? '';\n const translatedTerms = exclude.map((term) => formatTerm(term, prefix));\n\n if (translatedTerms.length === 1) {\n return `NOT ${translatedTerms[0]}`;\n }\n return `NOT (${translatedTerms.join(' OR ')})`;\n}\n\n/**\n * Translate ERIC Descriptors to subject: field syntax.\n */\nfunction translateEricDescriptors(descriptors: string[]): string[] {\n return descriptors.map((term) => formatTerm(term, 'subject:'));\n}\n\n/**\n * Translate a single query block to ERIC syntax.\n * Returns an object with the main query part and optional NOT clause.\n */\nfunction translateBlock(block: QueryBlock): { query: string; notClause: string | null } {\n const { field, terms, operator } = block;\n const keywords = terms.keywords;\n const eric = terms.eric ?? [];\n\n const allTerms: string[] = [];\n\n // Translate keywords\n if (keywords.length > 0) {\n // Handle title_abstract special case\n if (field === 'title_abstract') {\n const expandedTerms = keywords.map(translateTitleAbstractTerm);\n allTerms.push(...expandedTerms);\n } else {\n // Standard field translation\n const prefix = FIELD_PREFIXES[field] ?? '';\n const translatedTerms = keywords.map((term) => formatTerm(term, prefix));\n allTerms.push(...translatedTerms);\n }\n }\n\n // Translate ERIC Descriptors (always use subject: field)\n if (eric.length > 0) {\n const ericTerms = translateEricDescriptors(eric);\n allTerms.push(...ericTerms);\n }\n\n // Combine all terms\n let query = '';\n if (allTerms.length === 1) {\n query = allTerms[0]!;\n } else if (allTerms.length > 1) {\n query = `(${allTerms.join(` ${operator} `)})`;\n }\n\n // Translate exclude terms\n const notClause = terms.exclude\n ? translateExcludeTerms(terms.exclude, field)\n : null;\n\n return { query, notClause };\n}\n\n/**\n * Translate date filters to ERIC syntax.\n * ERIC uses publicationdateyear:[YYYY TO YYYY] format.\n */\nfunction translateDateFilters(filters: Filters): string | null {\n const { yearFrom, yearTo } = filters;\n\n if (yearFrom === undefined && yearTo === undefined) {\n return null;\n }\n\n const from = yearFrom !== undefined ? yearFrom.toString() : '*';\n const to = yearTo !== undefined ? yearTo.toString() : '*';\n\n return `publicationdateyear:[${from} TO ${to}]`;\n}\n\n/**\n * Translate a QueryAST to ERIC-native query syntax.\n */\nexport function translateQueryAST(ast: QueryAST): TranslatedQuery {\n const blockQueries: string[] = [];\n const notClauses: string[] = [];\n\n // Translate each block\n for (const block of ast.blocks) {\n const { query, notClause } = translateBlock(block);\n if (query) {\n blockQueries.push(query);\n }\n if (notClause) {\n notClauses.push(notClause);\n }\n }\n\n // Combine blocks with AND\n let native = blockQueries.join(' AND ');\n\n // Append NOT clauses\n for (const notClause of notClauses) {\n if (native) {\n native = `${native} ${notClause}`;\n } else {\n native = notClause;\n }\n }\n\n // Apply date filters\n const dateFilter = translateDateFilters(ast.filters);\n if (dateFilter) {\n if (native) {\n native = `${native} AND ${dateFilter}`;\n } else {\n native = dateFilter;\n }\n }\n\n return {\n native,\n originalAst: ast,\n provider: 'eric',\n };\n}\n\n/**\n * Translate a QueryAST to ERIC query.\n * This is the Provider interface method signature.\n */\nexport function translateQuery(ast: QueryAST): TranslatedQuery {\n return translateQueryAST(ast);\n}\n"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"translator.js","sources":["../../../src/providers/eric/translator.ts"],"sourcesContent":["/**\n * ERIC query translator.\n * Converts QueryAST to ERIC-native query syntax.\n */\n\nimport type { QueryAST, QueryBlock, Filters } from '../../query/types';\nimport type { TranslatedQuery } from '../base/types';\nimport { collectUnsupportedVocabWarnings } from '../base/warnings';\n\n/**\n * Field prefix mappings for ERIC.\n * Maps DSL field types to ERIC query syntax prefixes.\n */\nconst FIELD_PREFIXES: Record<string, string> = {\n title: 'title:',\n abstract: 'description:', // ERIC uses 'description' field for abstracts\n author: 'author:',\n keyword: 'subject:', // ERIC uses subject for descriptors\n all: '', // No prefix for all-field search\n};\n\n/**\n * Check if a term needs quoting (contains spaces or special characters).\n */\nfunction needsQuotes(term: string): boolean {\n return /\\s|[()[\\]{}:\"]/.test(term);\n}\n\n/**\n * Format a term with optional field prefix.\n * Quotes multi-word phrases automatically.\n */\nfunction formatTerm(term: string, prefix: string): string {\n const quoted = needsQuotes(term) ? `\"${term}\"` : term;\n return `${prefix}${quoted}`;\n}\n\n/**\n * Translate a single term for title_abstract field.\n * Expands to (title:term OR description:term).\n * Note: ERIC uses 'description' field for abstracts.\n */\nfunction translateTitleAbstractTerm(term: string): string {\n const quoted = needsQuotes(term) ? `\"${term}\"` : term;\n return `(title:${quoted} OR description:${quoted})`;\n}\n\n/**\n * Translate exclude terms to NOT clause.\n */\nfunction translateExcludeTerms(\n exclude: string[],\n field: QueryBlock['field']\n): string | null {\n if (exclude.length === 0) {\n return null;\n }\n\n // Handle title_abstract expansion\n if (field === 'title_abstract') {\n const expandedTerms = exclude.map(translateTitleAbstractTerm);\n if (expandedTerms.length === 1) {\n return `NOT ${expandedTerms[0]}`;\n }\n return `NOT (${expandedTerms.join(' OR ')})`;\n }\n\n // Standard field translation\n const prefix = FIELD_PREFIXES[field] ?? '';\n const translatedTerms = exclude.map((term) => formatTerm(term, prefix));\n\n if (translatedTerms.length === 1) {\n return `NOT ${translatedTerms[0]}`;\n }\n return `NOT (${translatedTerms.join(' OR ')})`;\n}\n\n/**\n * Translate ERIC Descriptors to subject: field syntax.\n */\nfunction translateEricDescriptors(descriptors: string[]): string[] {\n return descriptors.map((term) => formatTerm(term, 'subject:'));\n}\n\n/**\n * Translate a single query block to ERIC syntax.\n * Returns an object with the main query part and optional NOT clause.\n */\nfunction translateBlock(block: QueryBlock): { query: string; notClause: string | null } {\n const { field, terms, operator } = block;\n const keywords = terms.keywords ?? [];\n const eric = terms.eric ?? [];\n\n const allTerms: string[] = [];\n\n // Translate keywords\n if (keywords.length > 0) {\n // Handle title_abstract special case\n if (field === 'title_abstract') {\n const expandedTerms = keywords.map(translateTitleAbstractTerm);\n allTerms.push(...expandedTerms);\n } else {\n // Standard field translation\n const prefix = FIELD_PREFIXES[field] ?? '';\n const translatedTerms = keywords.map((term) => formatTerm(term, prefix));\n allTerms.push(...translatedTerms);\n }\n }\n\n // Translate ERIC Descriptors (always use subject: field)\n if (eric.length > 0) {\n const ericTerms = translateEricDescriptors(eric);\n allTerms.push(...ericTerms);\n }\n\n // Combine all terms\n let query = '';\n if (allTerms.length === 1) {\n query = allTerms[0]!;\n } else if (allTerms.length > 1) {\n query = `(${allTerms.join(` ${operator} `)})`;\n }\n\n // Translate exclude terms\n const notClause = terms.exclude\n ? translateExcludeTerms(terms.exclude, field)\n : null;\n\n return { query, notClause };\n}\n\n/**\n * Translate date filters to ERIC syntax.\n * ERIC uses publicationdateyear:[YYYY TO YYYY] format.\n */\nfunction translateDateFilters(filters: Filters): string | null {\n const { yearFrom, yearTo } = filters;\n\n if (yearFrom === undefined && yearTo === undefined) {\n return null;\n }\n\n const from = yearFrom !== undefined ? yearFrom.toString() : '*';\n const to = yearTo !== undefined ? yearTo.toString() : '*';\n\n return `publicationdateyear:[${from} TO ${to}]`;\n}\n\n/**\n * Translate a QueryAST to ERIC-native query syntax.\n */\nexport function translateQueryAST(ast: QueryAST): TranslatedQuery {\n const blockQueries: string[] = [];\n const notClauses: string[] = [];\n\n // Translate each block\n for (const block of ast.blocks) {\n const { query, notClause } = translateBlock(block);\n if (query) {\n blockQueries.push(query);\n }\n if (notClause) {\n notClauses.push(notClause);\n }\n }\n\n // Combine blocks with AND\n let native = blockQueries.join(' AND ');\n\n // Append NOT clauses\n for (const notClause of notClauses) {\n if (native) {\n native = `${native} ${notClause}`;\n } else {\n native = notClause;\n }\n }\n\n // Apply date filters\n const dateFilter = translateDateFilters(ast.filters);\n if (dateFilter) {\n if (native) {\n native = `${native} AND ${dateFilter}`;\n } else {\n native = dateFilter;\n }\n }\n\n // Collect warnings for unsupported controlled vocabulary\n // ERIC supports eric descriptors but not mesh or emtree\n const warnings = collectUnsupportedVocabWarnings(ast.blocks, 'ERIC', new Set(['eric']));\n\n return {\n native,\n originalAst: ast,\n provider: 'eric',\n ...(warnings.length > 0 ? { warnings } : {}),\n };\n}\n\n/**\n * Translate a QueryAST to ERIC query.\n * This is the Provider interface method signature.\n */\nexport function translateQuery(ast: QueryAST): TranslatedQuery {\n return translateQueryAST(ast);\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"translator.d.ts","sourceRoot":"","sources":["../../../src/providers/pubmed/translator.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAkC,MAAM,mBAAmB,CAAC;AAClF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"translator.d.ts","sourceRoot":"","sources":["../../../src/providers/pubmed/translator.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAkC,MAAM,mBAAmB,CAAC;AAClF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAyMrD;;GAEG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,QAAQ,GAAG,eAAe,CAuE7D"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { collectUnsupportedVocabWarnings } from "../base/warnings.js";
|
|
1
2
|
const FIELD_QUALIFIERS = {
|
|
2
3
|
title: "ti",
|
|
3
4
|
abstract: "ab",
|
|
@@ -44,7 +45,7 @@ function translateExcludeTerms(exclude, qualifier) {
|
|
|
44
45
|
function translateBlock(block) {
|
|
45
46
|
const qualifier = FIELD_QUALIFIERS[block.field];
|
|
46
47
|
const terms = [];
|
|
47
|
-
for (const keyword of block.terms.keywords) {
|
|
48
|
+
for (const keyword of block.terms.keywords ?? []) {
|
|
48
49
|
terms.push(translateTerm(keyword, qualifier));
|
|
49
50
|
}
|
|
50
51
|
if (block.terms.mesh) {
|
|
@@ -159,10 +160,12 @@ function translateQuery(ast) {
|
|
|
159
160
|
} else {
|
|
160
161
|
native = andSection;
|
|
161
162
|
}
|
|
163
|
+
const warnings = collectUnsupportedVocabWarnings(ast.blocks, "PubMed", /* @__PURE__ */ new Set(["mesh"]));
|
|
162
164
|
return {
|
|
163
165
|
native,
|
|
164
166
|
originalAst: ast,
|
|
165
|
-
provider: "pubmed"
|
|
167
|
+
provider: "pubmed",
|
|
168
|
+
...warnings.length > 0 ? { warnings } : {}
|
|
166
169
|
};
|
|
167
170
|
}
|
|
168
171
|
export {
|