@ncukondo/search-hub 0.13.0 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (100) hide show
  1. package/README.md +39 -9
  2. package/dist/cli/commands/check.d.ts +34 -0
  3. package/dist/cli/commands/check.d.ts.map +1 -0
  4. package/dist/cli/commands/check.js +126 -0
  5. package/dist/cli/commands/check.js.map +1 -0
  6. package/dist/cli/commands/export.d.ts +5 -3
  7. package/dist/cli/commands/export.d.ts.map +1 -1
  8. package/dist/cli/commands/export.js +0 -4
  9. package/dist/cli/commands/export.js.map +1 -1
  10. package/dist/cli/commands/query/init.d.ts.map +1 -1
  11. package/dist/cli/commands/query/init.js +17 -7
  12. package/dist/cli/commands/query/init.js.map +1 -1
  13. package/dist/cli/commands/query/inspect.d.ts +36 -0
  14. package/dist/cli/commands/query/inspect.d.ts.map +1 -0
  15. package/dist/cli/commands/query/inspect.js +155 -0
  16. package/dist/cli/commands/query/inspect.js.map +1 -0
  17. package/dist/cli/commands/query/translate.d.ts.map +1 -1
  18. package/dist/cli/commands/query/translate.js +3 -1
  19. package/dist/cli/commands/query/translate.js.map +1 -1
  20. package/dist/cli/commands/query-filter.d.ts +13 -0
  21. package/dist/cli/commands/query-filter.d.ts.map +1 -0
  22. package/dist/cli/commands/query-filter.js +149 -0
  23. package/dist/cli/commands/query-filter.js.map +1 -0
  24. package/dist/cli/commands/results.d.ts +3 -3
  25. package/dist/cli/commands/results.d.ts.map +1 -1
  26. package/dist/cli/commands/results.js +12 -3
  27. package/dist/cli/commands/results.js.map +1 -1
  28. package/dist/cli/commands/search-executor.d.ts.map +1 -1
  29. package/dist/cli/commands/search-executor.js +12 -7
  30. package/dist/cli/commands/search-executor.js.map +1 -1
  31. package/dist/cli/e2e-helpers.d.ts +5 -2
  32. package/dist/cli/e2e-helpers.d.ts.map +1 -1
  33. package/dist/cli/index.d.ts.map +1 -1
  34. package/dist/cli/index.js +228 -45
  35. package/dist/cli/index.js.map +1 -1
  36. package/dist/index.js +4 -2
  37. package/dist/index.js.map +1 -1
  38. package/dist/providers/arxiv/provider.d.ts +3 -3
  39. package/dist/providers/arxiv/provider.d.ts.map +1 -1
  40. package/dist/providers/arxiv/provider.js +3 -3
  41. package/dist/providers/arxiv/provider.js.map +1 -1
  42. package/dist/providers/arxiv/translator.d.ts +3 -3
  43. package/dist/providers/arxiv/translator.d.ts.map +1 -1
  44. package/dist/providers/arxiv/translator.js +6 -8
  45. package/dist/providers/arxiv/translator.js.map +1 -1
  46. package/dist/providers/base/index.d.ts +1 -1
  47. package/dist/providers/base/index.d.ts.map +1 -1
  48. package/dist/providers/base/mock-provider.d.ts +2 -2
  49. package/dist/providers/base/mock-provider.d.ts.map +1 -1
  50. package/dist/providers/base/mock-provider.js +2 -9
  51. package/dist/providers/base/mock-provider.js.map +1 -1
  52. package/dist/providers/base/provider.d.ts +3 -3
  53. package/dist/providers/base/provider.d.ts.map +1 -1
  54. package/dist/providers/base/provider.js.map +1 -1
  55. package/dist/providers/base/types.d.ts +4 -6
  56. package/dist/providers/base/types.d.ts.map +1 -1
  57. package/dist/providers/base/types.js.map +1 -1
  58. package/dist/providers/eric/provider.d.ts +3 -3
  59. package/dist/providers/eric/provider.d.ts.map +1 -1
  60. package/dist/providers/eric/provider.js +3 -3
  61. package/dist/providers/eric/provider.js.map +1 -1
  62. package/dist/providers/eric/translator.d.ts +5 -5
  63. package/dist/providers/eric/translator.d.ts.map +1 -1
  64. package/dist/providers/eric/translator.js +6 -7
  65. package/dist/providers/eric/translator.js.map +1 -1
  66. package/dist/providers/pubmed/provider.d.ts +3 -3
  67. package/dist/providers/pubmed/provider.d.ts.map +1 -1
  68. package/dist/providers/pubmed/provider.js +3 -3
  69. package/dist/providers/pubmed/provider.js.map +1 -1
  70. package/dist/providers/pubmed/translator.d.ts +3 -3
  71. package/dist/providers/pubmed/translator.d.ts.map +1 -1
  72. package/dist/providers/pubmed/translator.js +4 -23
  73. package/dist/providers/pubmed/translator.js.map +1 -1
  74. package/dist/providers/scopus/provider.d.ts +3 -3
  75. package/dist/providers/scopus/provider.d.ts.map +1 -1
  76. package/dist/providers/scopus/provider.js +3 -3
  77. package/dist/providers/scopus/provider.js.map +1 -1
  78. package/dist/providers/scopus/translator.d.ts +3 -3
  79. package/dist/providers/scopus/translator.d.ts.map +1 -1
  80. package/dist/providers/scopus/translator.js +7 -9
  81. package/dist/providers/scopus/translator.js.map +1 -1
  82. package/dist/query/index.d.ts +3 -2
  83. package/dist/query/index.d.ts.map +1 -1
  84. package/dist/query/json-schema.d.ts.map +1 -1
  85. package/dist/query/json-schema.js +20 -11
  86. package/dist/query/json-schema.js.map +1 -1
  87. package/dist/query/mesh-lookup.d.ts.map +1 -1
  88. package/dist/query/mesh-lookup.js +66 -3
  89. package/dist/query/mesh-lookup.js.map +1 -1
  90. package/dist/query/resolver.d.ts +14 -0
  91. package/dist/query/resolver.d.ts.map +1 -0
  92. package/dist/query/resolver.js +61 -0
  93. package/dist/query/resolver.js.map +1 -0
  94. package/dist/query/types.d.ts +31 -11
  95. package/dist/query/types.d.ts.map +1 -1
  96. package/dist/query/validator.d.ts +659 -348
  97. package/dist/query/validator.d.ts.map +1 -1
  98. package/dist/query/validator.js +70 -30
  99. package/dist/query/validator.js.map +1 -1
  100. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"translator.js","sources":["../../../src/providers/scopus/translator.ts"],"sourcesContent":["/**\n * Scopus Query Translator\n *\n * Translates QueryAST to Scopus search syntax.\n */\n\nimport type { QueryAST, FieldType, QueryBlock, Filters, OverrideBlock } from '../../query/types';\nimport type { TranslatedQuery } from '../base/types';\nimport { collectUnsupportedVocabWarnings } from '../base/warnings';\n\n/**\n * Field function mappings for Scopus.\n */\nconst FIELD_MAP: Record<FieldType, string> = {\n title: 'TITLE',\n abstract: 'ABS',\n title_abstract: 'TITLE-ABS-KEY',\n author: 'AUTH',\n keyword: 'KEY',\n all: 'ALL',\n};\n\n/**\n * Source type code mappings for Scopus.\n */\nconst SOURCE_TYPE_MAP: Record<string, string> = {\n journal: 'j',\n conference: 'p',\n book: 'b',\n 'book series': 'k',\n 'trade journal': 'd',\n};\n\n/**\n * Language code mappings for Scopus.\n */\nconst LANGUAGE_MAP: Record<string, string> = {\n en: 'english',\n de: 'german',\n fr: 'french',\n es: 'spanish',\n it: 'italian',\n ja: 'japanese',\n zh: 'chinese',\n ko: 'korean',\n pt: 'portuguese',\n ru: 'russian',\n};\n\n/**\n * Check if a term needs to be quoted (contains spaces and isn't already quoted).\n */\nfunction needsQuotes(term: string): boolean {\n if (term.startsWith('\"') && term.endsWith('\"')) {\n return false;\n }\n if (term.startsWith('{') && term.endsWith('}')) {\n return false;\n }\n return term.includes(' ');\n}\n\n/**\n * Quote a term if it contains spaces.\n */\nfunction quoteTerm(term: string): string {\n if (needsQuotes(term)) {\n return `\"${term}\"`;\n }\n return term;\n}\n\n/**\n * Translate a single query block to Scopus 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 = FIELD_MAP[block.field];\n const operator = block.operator;\n const parts: string[] = [];\n\n // Translate keywords\n const keywords = (block.terms.keywords ?? []).map(quoteTerm);\n if (keywords.length > 0) {\n parts.push(`${field}(${keywords.join(` ${operator} `)})`);\n }\n\n // Translate Emtree terms (always use INDEXTERMS)\n const emtree = (block.terms.emtree ?? []).map(quoteTerm);\n if (emtree.length > 0) {\n parts.push(`INDEXTERMS(${emtree.join(` ${operator} `)})`);\n }\n\n // Combine parts (empty string when no supported terms)\n let query: string;\n if (parts.length === 0) {\n query = '';\n } else if (parts.length === 1) {\n query = parts[0]!;\n } else {\n query = parts.join(` ${operator} `);\n }\n\n // Translate exclude terms (without AND prefix - will be added during join)\n let notClause: string | null = null;\n if (block.terms.exclude && block.terms.exclude.length > 0) {\n const excludeTerms = block.terms.exclude.map(quoteTerm);\n const excludeStr = excludeTerms.join(' OR ');\n notClause = `NOT ${field}(${excludeStr})`;\n }\n\n return { query, notClause };\n}\n\n/**\n * Translate filters to Scopus syntax.\n */\nfunction translateFilters(filters: Filters, scopusOverrides?: OverrideBlock): string[] {\n const parts: string[] = [];\n\n // Year filters\n if (filters.yearFrom !== undefined) {\n parts.push(`PUBYEAR > ${filters.yearFrom - 1}`);\n }\n if (filters.yearTo !== undefined) {\n parts.push(`PUBYEAR < ${filters.yearTo + 1}`);\n }\n\n // Language filter\n if (filters.languages && filters.languages.length > 0) {\n const languages = filters.languages\n .map(code => LANGUAGE_MAP[code] || code)\n .join(' OR ');\n parts.push(`LANGUAGE(${languages})`);\n }\n\n // Source type filter from overrides\n if (scopusOverrides?.sourceTypes && scopusOverrides.sourceTypes.length > 0) {\n const sourceTypes = scopusOverrides.sourceTypes\n .map(type => SOURCE_TYPE_MAP[type] || type)\n .join(' OR ');\n parts.push(`SRCTYPE(${sourceTypes})`);\n }\n\n return parts;\n}\n\n/**\n * Translate a QueryAST to Scopus search syntax.\n */\nexport function translateQuery(ast: QueryAST): TranslatedQuery {\n // Translate query blocks\n const blockResults = ast.blocks.map(translateBlock);\n\n // Collect query parts (filter empty blocks) and NOT clauses\n const blockParts = blockResults\n .map((r) => r.query)\n .filter((s) => s.length > 0);\n const notClauses = blockResults\n .map((r) => r.notClause)\n .filter((s): s is string => s !== null);\n\n // Translate filters\n const scopusOverrides = ast.overrides.scopus;\n const filterParts = translateFilters(ast.filters, scopusOverrides);\n\n // Build native query: blocks AND NOT(excludes) AND filters\n const allParts: string[] = [...blockParts, ...notClauses, ...filterParts];\n const native = allParts.join(' AND ');\n\n // Collect warnings for unsupported controlled vocabulary\n // Scopus supports emtree but not mesh or eric\n const warnings = collectUnsupportedVocabWarnings(ast.blocks, 'Scopus', new Set(['emtree']));\n\n return {\n native,\n originalAst: ast,\n provider: 'scopus',\n ...(warnings.length > 0 ? { warnings } : {}),\n };\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"translator.js","sources":["../../../src/providers/scopus/translator.ts"],"sourcesContent":["/**\n * Scopus Query Translator\n *\n * Translates ResolvedAST to Scopus search syntax.\n */\n\nimport type { FieldType, QueryBlock, Filters, ResolvedAST } from '../../query/types';\nimport type { TranslatedQuery } from '../base/types';\nimport { collectUnsupportedVocabWarnings } from '../base/warnings';\n\n/**\n * Field function mappings for Scopus.\n */\nconst FIELD_MAP: Record<FieldType, string> = {\n title: 'TITLE',\n abstract: 'ABS',\n title_abstract: 'TITLE-ABS-KEY',\n author: 'AUTH',\n keyword: 'KEY',\n all: 'ALL',\n};\n\n/**\n * Source type code mappings for Scopus.\n */\nconst SOURCE_TYPE_MAP: Record<string, string> = {\n journal: 'j',\n conference: 'p',\n book: 'b',\n 'book series': 'k',\n 'trade journal': 'd',\n};\n\n/**\n * Language code mappings for Scopus.\n */\nconst LANGUAGE_MAP: Record<string, string> = {\n en: 'english',\n de: 'german',\n fr: 'french',\n es: 'spanish',\n it: 'italian',\n ja: 'japanese',\n zh: 'chinese',\n ko: 'korean',\n pt: 'portuguese',\n ru: 'russian',\n};\n\n/**\n * Check if a term needs to be quoted (contains spaces and isn't already quoted).\n */\nfunction needsQuotes(term: string): boolean {\n if (term.startsWith('\"') && term.endsWith('\"')) {\n return false;\n }\n if (term.startsWith('{') && term.endsWith('}')) {\n return false;\n }\n return term.includes(' ');\n}\n\n/**\n * Quote a term if it contains spaces.\n */\nfunction quoteTerm(term: string): string {\n if (needsQuotes(term)) {\n return `\"${term}\"`;\n }\n return term;\n}\n\n/**\n * Translate a single query block to Scopus 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 = FIELD_MAP[block.field];\n const operator = block.operator;\n const parts: string[] = [];\n\n // Translate keywords\n const keywords = (block.terms.keywords ?? []).map(quoteTerm);\n if (keywords.length > 0) {\n parts.push(`${field}(${keywords.join(` ${operator} `)})`);\n }\n\n // Translate Emtree terms (always use INDEXTERMS)\n const emtree = (block.terms.emtree ?? []).map(quoteTerm);\n if (emtree.length > 0) {\n parts.push(`INDEXTERMS(${emtree.join(` ${operator} `)})`);\n }\n\n // Combine parts (empty string when no supported terms)\n let query: string;\n if (parts.length === 0) {\n query = '';\n } else if (parts.length === 1) {\n query = parts[0]!;\n } else {\n query = parts.join(` ${operator} `);\n }\n\n // Translate exclude terms (without AND prefix - will be added during join)\n let notClause: string | null = null;\n if (block.terms.exclude && block.terms.exclude.length > 0) {\n const excludeTerms = block.terms.exclude.map(quoteTerm);\n const excludeStr = excludeTerms.join(' OR ');\n notClause = `NOT ${field}(${excludeStr})`;\n }\n\n return { query, notClause };\n}\n\n/**\n * Translate filters to Scopus syntax.\n * sourceTypes now comes from resolved filters (was in overrides).\n */\nfunction translateFilters(filters: Filters): string[] {\n const parts: string[] = [];\n\n // Year filters\n if (filters.yearFrom !== undefined) {\n parts.push(`PUBYEAR > ${filters.yearFrom - 1}`);\n }\n if (filters.yearTo !== undefined) {\n parts.push(`PUBYEAR < ${filters.yearTo + 1}`);\n }\n\n // Language filter\n if (filters.languages && filters.languages.length > 0) {\n const languages = filters.languages\n .map(code => LANGUAGE_MAP[code] || code)\n .join(' OR ');\n parts.push(`LANGUAGE(${languages})`);\n }\n\n // Source type filter (now from resolved filters)\n if (filters.sourceTypes && filters.sourceTypes.length > 0) {\n const sourceTypes = filters.sourceTypes\n .map(type => SOURCE_TYPE_MAP[type] || type)\n .join(' OR ');\n parts.push(`SRCTYPE(${sourceTypes})`);\n }\n\n return parts;\n}\n\n/**\n * Translate a ResolvedAST to Scopus search syntax.\n */\nexport function translateQuery(resolved: ResolvedAST): TranslatedQuery {\n // Translate query blocks\n const blockResults = resolved.blocks.map(translateBlock);\n\n // Collect query parts (filter empty blocks) and NOT clauses\n const blockParts = blockResults\n .map((r) => r.query)\n .filter((s) => s.length > 0);\n const notClauses = blockResults\n .map((r) => r.notClause)\n .filter((s): s is string => s !== null);\n\n // Translate filters (sourceTypes now in resolved.filters)\n const filterParts = translateFilters(resolved.filters);\n\n // Build native query: blocks AND NOT(excludes) AND filters\n const allParts: string[] = [...blockParts, ...notClauses, ...filterParts];\n const native = allParts.join(' AND ');\n\n // Collect warnings for unsupported controlled vocabulary\n // Scopus supports emtree but not mesh or eric\n const warnings = collectUnsupportedVocabWarnings(resolved.blocks, 'Scopus', new Set(['emtree']));\n\n return {\n native,\n provider: 'scopus',\n ...(warnings.length > 0 ? { warnings } : {}),\n };\n}\n"],"names":[],"mappings":"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"}
@@ -6,9 +6,10 @@
6
6
  *
7
7
  * @module query
8
8
  */
9
- export type { FieldType, Operator, ProviderName, TermBlock, QueryBlock, Filters, PublicationTypeFilter, OverrideBlock, QueryAST, } from './types.js';
9
+ export type { FieldType, Operator, ProviderName, TermBlock, QueryBlock, Filters, PublicationTypeFilter, ProviderSection, QueryAST, ResolvedAST, } from './types.js';
10
10
  export { parseQueryFile, parseQueryString } from './parser.js';
11
- export { validateQueryFile, formatValidationErrors, ValidationError, fieldTypeSchema, termBlockSchema, queryBlockSchema, filtersSchema, overrideBlockSchema, queryFileSchema, } from './validator.js';
11
+ export { resolveForProvider } from './resolver.js';
12
+ export { validateQueryFile, formatValidationErrors, ValidationError, fieldTypeSchema, termBlockSchema, queryBlockSchema, filtersSchema, providerSectionSchema, queryFileSchema, } from './validator.js';
12
13
  export { MeSHLookupClient } from './mesh-lookup.js';
13
14
  export type { MeSHLookupResult } from './mesh-lookup.js';
14
15
  export { extractControlledVocabTerms, validateControlledVocab, } from './vocab-validator.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/query/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,YAAY,EACV,SAAS,EACT,QAAQ,EACR,YAAY,EACZ,SAAS,EACT,UAAU,EACV,OAAO,EACP,qBAAqB,EACrB,aAAa,EACb,QAAQ,GACT,MAAM,YAAY,CAAC;AAGpB,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAG/D,OAAO,EACL,iBAAiB,EACjB,sBAAsB,EACtB,eAAe,EAEf,eAAe,EACf,eAAe,EACf,gBAAgB,EAChB,aAAa,EACb,mBAAmB,EACnB,eAAe,GAChB,MAAM,gBAAgB,CAAC;AAGxB,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACpD,YAAY,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACzD,OAAO,EACL,2BAA2B,EAC3B,uBAAuB,GACxB,MAAM,sBAAsB,CAAC;AAC9B,YAAY,EACV,SAAS,EACT,cAAc,EACd,eAAe,EACf,qBAAqB,GACtB,MAAM,sBAAsB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/query/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,YAAY,EACV,SAAS,EACT,QAAQ,EACR,YAAY,EACZ,SAAS,EACT,UAAU,EACV,OAAO,EACP,qBAAqB,EACrB,eAAe,EACf,QAAQ,EACR,WAAW,GACZ,MAAM,YAAY,CAAC;AAGpB,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAG/D,OAAO,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAGnD,OAAO,EACL,iBAAiB,EACjB,sBAAsB,EACtB,eAAe,EAEf,eAAe,EACf,eAAe,EACf,gBAAgB,EAChB,aAAa,EACb,qBAAqB,EACrB,eAAe,GAChB,MAAM,gBAAgB,CAAC;AAGxB,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACpD,YAAY,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACzD,OAAO,EACL,2BAA2B,EAC3B,uBAAuB,GACxB,MAAM,sBAAsB,CAAC;AAC9B,YAAY,EACV,SAAS,EACT,cAAc,EACd,eAAe,EACf,qBAAqB,GACtB,MAAM,sBAAsB,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"json-schema.d.ts","sourceRoot":"","sources":["../../src/query/json-schema.ts"],"names":[],"mappings":"AAsEA,6DAA6D;AAC7D,wBAAgB,uBAAuB,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAIjE"}
1
+ {"version":3,"file":"json-schema.d.ts","sourceRoot":"","sources":["../../src/query/json-schema.ts"],"names":[],"mappings":"AAmFA,6DAA6D;AAC7D,wBAAgB,uBAAuB,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAIjE"}
@@ -4,10 +4,7 @@ const filtersInputSchema = z.object({
4
4
  year_from: z.number().int().optional(),
5
5
  year_to: z.number().int().optional(),
6
6
  language: z.array(z.string()).optional(),
7
- publication_types: publicationTypeFilterSchema.optional()
8
- }).optional();
9
- const overrideBlockInputSchema = z.object({
10
- filters: filtersInputSchema,
7
+ publication_types: publicationTypeFilterSchema.optional(),
11
8
  categories: z.array(z.string()).optional(),
12
9
  source_types: z.array(z.string()).optional()
13
10
  }).optional();
@@ -19,22 +16,34 @@ const termBlockInputSchema = z.object({
19
16
  exclude: z.array(z.string()).optional()
20
17
  });
21
18
  const queryBlockInputSchema = z.object({
19
+ id: z.string().min(1),
20
+ field: fieldTypeSchema,
21
+ terms: termBlockInputSchema,
22
+ operator: operatorSchema
23
+ });
24
+ const blockReplacementInputSchema = z.object({
22
25
  field: fieldTypeSchema,
23
26
  terms: termBlockInputSchema,
24
27
  operator: operatorSchema
25
28
  });
29
+ const providerSectionInputSchema = z.object({
30
+ replaces: z.record(z.string(), blockReplacementInputSchema).optional(),
31
+ adds: z.object({
32
+ filters: filtersInputSchema
33
+ }).optional()
34
+ }).optional();
26
35
  const queryFileInputSchema = z.object({
27
36
  name: z.string().min(1),
28
37
  description: z.string().optional(),
29
38
  query: z.array(queryBlockInputSchema).min(1),
30
39
  filters: filtersInputSchema,
31
- overrides: z.object({
32
- pubmed: overrideBlockInputSchema,
33
- scopus: overrideBlockInputSchema,
34
- eric: overrideBlockInputSchema,
35
- arxiv: overrideBlockInputSchema,
36
- wos: overrideBlockInputSchema,
37
- embase: overrideBlockInputSchema
40
+ providers: z.object({
41
+ pubmed: providerSectionInputSchema,
42
+ scopus: providerSectionInputSchema,
43
+ eric: providerSectionInputSchema,
44
+ arxiv: providerSectionInputSchema,
45
+ wos: providerSectionInputSchema,
46
+ embase: providerSectionInputSchema
38
47
  }).optional()
39
48
  });
40
49
  function generateQueryJSONSchema() {
@@ -1 +1 @@
1
- {"version":3,"file":"json-schema.js","sources":["../../src/query/json-schema.ts"],"sourcesContent":["/**\n * JSON Schema generation for query YAML files.\n *\n * Defines an input-only schema (without transforms) that mirrors the structure\n * of queryFileSchema from validator.ts. This is necessary because Zod v4's\n * z.toJSONSchema() cannot handle .transform() calls.\n *\n * The generated JSON Schema enables editor autocompletion and validation\n * via the yaml-language-server $schema comment.\n */\nimport * as z from 'zod';\nimport {\n fieldTypeSchema,\n operatorSchema,\n publicationTypeFilterSchema,\n} from './validator.js';\n\n/** Filters input schema (without transform) */\nconst filtersInputSchema = z\n .object({\n year_from: z.number().int().optional(),\n year_to: z.number().int().optional(),\n language: z.array(z.string()).optional(),\n publication_types: publicationTypeFilterSchema.optional(),\n })\n .optional();\n\n/** Override block input schema (without transform) */\nconst overrideBlockInputSchema = z\n .object({\n filters: filtersInputSchema,\n categories: z.array(z.string()).optional(),\n source_types: z.array(z.string()).optional(),\n })\n .optional();\n\n/** Term block input schema (without refine) */\nconst termBlockInputSchema = z.object({\n keywords: z.array(z.string()).min(1).optional(),\n mesh: z.array(z.string()).optional(),\n emtree: z.array(z.string()).optional(),\n eric: z.array(z.string()).optional(),\n exclude: z.array(z.string()).optional(),\n});\n\n/** Query block input schema */\nconst queryBlockInputSchema = z.object({\n field: fieldTypeSchema,\n terms: termBlockInputSchema,\n operator: operatorSchema,\n});\n\n/** Query file input schema (without transform) - mirrors queryFileSchema input */\nconst queryFileInputSchema = z.object({\n name: z.string().min(1),\n description: z.string().optional(),\n query: z.array(queryBlockInputSchema).min(1),\n filters: filtersInputSchema,\n overrides: z\n .object({\n pubmed: overrideBlockInputSchema,\n scopus: overrideBlockInputSchema,\n eric: overrideBlockInputSchema,\n arxiv: overrideBlockInputSchema,\n wos: overrideBlockInputSchema,\n embase: overrideBlockInputSchema,\n })\n .optional(),\n});\n\n/** Generate a JSON Schema from the query file Zod schema. */\nexport function generateQueryJSONSchema(): Record<string, unknown> {\n return z.toJSONSchema(queryFileInputSchema, {\n target: 'draft-2020-12',\n });\n}\n"],"names":[],"mappings":";;AAkBA,MAAM,qBAAqB,EACxB,OAAO;AAAA,EACN,WAAW,EAAE,OAAA,EAAS,IAAA,EAAM,SAAA;AAAA,EAC5B,SAAS,EAAE,OAAA,EAAS,IAAA,EAAM,SAAA;AAAA,EAC1B,UAAU,EAAE,MAAM,EAAE,OAAA,CAAQ,EAAE,SAAA;AAAA,EAC9B,mBAAmB,4BAA4B,SAAA;AACjD,CAAC,EACA,SAAA;AAGH,MAAM,2BAA2B,EAC9B,OAAO;AAAA,EACN,SAAS;AAAA,EACT,YAAY,EAAE,MAAM,EAAE,OAAA,CAAQ,EAAE,SAAA;AAAA,EAChC,cAAc,EAAE,MAAM,EAAE,OAAA,CAAQ,EAAE,SAAA;AACpC,CAAC,EACA,SAAA;AAGH,MAAM,uBAAuB,EAAE,OAAO;AAAA,EACpC,UAAU,EAAE,MAAM,EAAE,OAAA,CAAQ,EAAE,IAAI,CAAC,EAAE,SAAA;AAAA,EACrC,MAAM,EAAE,MAAM,EAAE,OAAA,CAAQ,EAAE,SAAA;AAAA,EAC1B,QAAQ,EAAE,MAAM,EAAE,OAAA,CAAQ,EAAE,SAAA;AAAA,EAC5B,MAAM,EAAE,MAAM,EAAE,OAAA,CAAQ,EAAE,SAAA;AAAA,EAC1B,SAAS,EAAE,MAAM,EAAE,OAAA,CAAQ,EAAE,SAAA;AAC/B,CAAC;AAGD,MAAM,wBAAwB,EAAE,OAAO;AAAA,EACrC,OAAO;AAAA,EACP,OAAO;AAAA,EACP,UAAU;AACZ,CAAC;AAGD,MAAM,uBAAuB,EAAE,OAAO;AAAA,EACpC,MAAM,EAAE,SAAS,IAAI,CAAC;AAAA,EACtB,aAAa,EAAE,OAAA,EAAS,SAAA;AAAA,EACxB,OAAO,EAAE,MAAM,qBAAqB,EAAE,IAAI,CAAC;AAAA,EAC3C,SAAS;AAAA,EACT,WAAW,EACR,OAAO;AAAA,IACN,QAAQ;AAAA,IACR,QAAQ;AAAA,IACR,MAAM;AAAA,IACN,OAAO;AAAA,IACP,KAAK;AAAA,IACL,QAAQ;AAAA,EAAA,CACT,EACA,SAAA;AACL,CAAC;AAGM,SAAS,0BAAmD;AACjE,SAAO,EAAE,aAAa,sBAAsB;AAAA,IAC1C,QAAQ;AAAA,EAAA,CACT;AACH;"}
1
+ {"version":3,"file":"json-schema.js","sources":["../../src/query/json-schema.ts"],"sourcesContent":["/**\n * JSON Schema generation for query YAML files.\n *\n * Defines an input-only schema (without transforms) that mirrors the structure\n * of queryFileSchema from validator.ts. This is necessary because Zod v4's\n * z.toJSONSchema() cannot handle .transform() calls.\n *\n * The generated JSON Schema enables editor autocompletion and validation\n * via the yaml-language-server $schema comment.\n */\nimport * as z from 'zod';\nimport {\n fieldTypeSchema,\n operatorSchema,\n publicationTypeFilterSchema,\n} from './validator.js';\n\n/** Filters input schema (without transform) */\nconst filtersInputSchema = z\n .object({\n year_from: z.number().int().optional(),\n year_to: z.number().int().optional(),\n language: z.array(z.string()).optional(),\n publication_types: publicationTypeFilterSchema.optional(),\n categories: z.array(z.string()).optional(),\n source_types: z.array(z.string()).optional(),\n })\n .optional();\n\n/** Term block input schema (without refine) */\nconst termBlockInputSchema = z.object({\n keywords: z.array(z.string()).min(1).optional(),\n mesh: z.array(z.string()).optional(),\n emtree: z.array(z.string()).optional(),\n eric: z.array(z.string()).optional(),\n exclude: z.array(z.string()).optional(),\n});\n\n/** Query block input schema */\nconst queryBlockInputSchema = z.object({\n id: z.string().min(1),\n field: fieldTypeSchema,\n terms: termBlockInputSchema,\n operator: operatorSchema,\n});\n\n/** Block replacement input schema (without id) */\nconst blockReplacementInputSchema = z.object({\n field: fieldTypeSchema,\n terms: termBlockInputSchema,\n operator: operatorSchema,\n});\n\n/** Provider section input schema */\nconst providerSectionInputSchema = z\n .object({\n replaces: z.record(z.string(), blockReplacementInputSchema).optional(),\n adds: z\n .object({\n filters: filtersInputSchema,\n })\n .optional(),\n })\n .optional();\n\n/** Query file input schema (without transform) - mirrors queryFileSchema input */\nconst queryFileInputSchema = z.object({\n name: z.string().min(1),\n description: z.string().optional(),\n query: z.array(queryBlockInputSchema).min(1),\n filters: filtersInputSchema,\n providers: z\n .object({\n pubmed: providerSectionInputSchema,\n scopus: providerSectionInputSchema,\n eric: providerSectionInputSchema,\n arxiv: providerSectionInputSchema,\n wos: providerSectionInputSchema,\n embase: providerSectionInputSchema,\n })\n .optional(),\n});\n\n/** Generate a JSON Schema from the query file Zod schema. */\nexport function generateQueryJSONSchema(): Record<string, unknown> {\n return z.toJSONSchema(queryFileInputSchema, {\n target: 'draft-2020-12',\n });\n}\n"],"names":[],"mappings":";;AAkBA,MAAM,qBAAqB,EACxB,OAAO;AAAA,EACN,WAAW,EAAE,OAAA,EAAS,IAAA,EAAM,SAAA;AAAA,EAC5B,SAAS,EAAE,OAAA,EAAS,IAAA,EAAM,SAAA;AAAA,EAC1B,UAAU,EAAE,MAAM,EAAE,OAAA,CAAQ,EAAE,SAAA;AAAA,EAC9B,mBAAmB,4BAA4B,SAAA;AAAA,EAC/C,YAAY,EAAE,MAAM,EAAE,OAAA,CAAQ,EAAE,SAAA;AAAA,EAChC,cAAc,EAAE,MAAM,EAAE,OAAA,CAAQ,EAAE,SAAA;AACpC,CAAC,EACA,SAAA;AAGH,MAAM,uBAAuB,EAAE,OAAO;AAAA,EACpC,UAAU,EAAE,MAAM,EAAE,OAAA,CAAQ,EAAE,IAAI,CAAC,EAAE,SAAA;AAAA,EACrC,MAAM,EAAE,MAAM,EAAE,OAAA,CAAQ,EAAE,SAAA;AAAA,EAC1B,QAAQ,EAAE,MAAM,EAAE,OAAA,CAAQ,EAAE,SAAA;AAAA,EAC5B,MAAM,EAAE,MAAM,EAAE,OAAA,CAAQ,EAAE,SAAA;AAAA,EAC1B,SAAS,EAAE,MAAM,EAAE,OAAA,CAAQ,EAAE,SAAA;AAC/B,CAAC;AAGD,MAAM,wBAAwB,EAAE,OAAO;AAAA,EACrC,IAAI,EAAE,SAAS,IAAI,CAAC;AAAA,EACpB,OAAO;AAAA,EACP,OAAO;AAAA,EACP,UAAU;AACZ,CAAC;AAGD,MAAM,8BAA8B,EAAE,OAAO;AAAA,EAC3C,OAAO;AAAA,EACP,OAAO;AAAA,EACP,UAAU;AACZ,CAAC;AAGD,MAAM,6BAA6B,EAChC,OAAO;AAAA,EACN,UAAU,EAAE,OAAO,EAAE,UAAU,2BAA2B,EAAE,SAAA;AAAA,EAC5D,MAAM,EACH,OAAO;AAAA,IACN,SAAS;AAAA,EAAA,CACV,EACA,SAAA;AACL,CAAC,EACA,SAAA;AAGH,MAAM,uBAAuB,EAAE,OAAO;AAAA,EACpC,MAAM,EAAE,SAAS,IAAI,CAAC;AAAA,EACtB,aAAa,EAAE,OAAA,EAAS,SAAA;AAAA,EACxB,OAAO,EAAE,MAAM,qBAAqB,EAAE,IAAI,CAAC;AAAA,EAC3C,SAAS;AAAA,EACT,WAAW,EACR,OAAO;AAAA,IACN,QAAQ;AAAA,IACR,QAAQ;AAAA,IACR,MAAM;AAAA,IACN,OAAO;AAAA,IACP,KAAK;AAAA,IACL,QAAQ;AAAA,EAAA,CACT,EACA,SAAA;AACL,CAAC;AAGM,SAAS,0BAAmD;AACjE,SAAO,EAAE,aAAa,sBAAsB;AAAA,IAC1C,QAAQ;AAAA,EAAA,CACT;AACH;"}
@@ -1 +1 @@
1
- {"version":3,"file":"mesh-lookup.d.ts","sourceRoot":"","sources":["../../src/query/mesh-lookup.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mCAAmC,CAAC;AACrE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAMnD;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,kCAAkC;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,yDAAyD;IACzD,KAAK,EAAE,OAAO,CAAC;IACf,uDAAuD;IACvD,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;CACxB;AAOD;;GAEG;AACH,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,QAAQ,CAAC,WAAW,CAA0B;IACtD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAyB;gBAEnC,OAAO,CAAC,EAAE;QAAE,WAAW,CAAC,EAAE,WAAW,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,UAAU,CAAA;KAAE;IAM3F;;;;;;;;;;;;;OAaG;IACG,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC;IA4GzD;;OAEG;IACG,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC;YAQjD,WAAW;CAoC1B"}
1
+ {"version":3,"file":"mesh-lookup.d.ts","sourceRoot":"","sources":["../../src/query/mesh-lookup.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mCAAmC,CAAC;AACrE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAMnD;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,kCAAkC;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,yDAAyD;IACzD,KAAK,EAAE,OAAO,CAAC;IACf,uDAAuD;IACvD,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;CACxB;AAOD;;GAEG;AACH,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,QAAQ,CAAC,WAAW,CAA0B;IACtD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAyB;gBAEnC,OAAO,CAAC,EAAE;QAAE,WAAW,CAAC,EAAE,WAAW,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,UAAU,CAAA;KAAE;IAM3F;;;;;;;;;;;;;OAaG;IACG,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAmMzD;;OAEG;IACG,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC;YAQjD,WAAW;CAoC1B"}
@@ -93,13 +93,76 @@ class MeSHLookupClient {
93
93
  }
94
94
  if (words.length > 1) {
95
95
  const firstWord = words[0];
96
+ const restWords = words.slice(1).join(" ").toLowerCase();
97
+ const restWordsPrefix = restWords.slice(0, 4);
96
98
  const firstWordResults = await this.fetchLookup(firstWord, "startswith", 25);
97
99
  if (firstWordResults.length > 0) {
98
- const ranked = firstWordResults.map((s) => ({
100
+ const candidates = restWordsPrefix.length >= 4 ? firstWordResults.filter(
101
+ (r) => r.label.toLowerCase().includes(restWordsPrefix)
102
+ ) : firstWordResults;
103
+ if (candidates.length > 0) {
104
+ const ranked = candidates.map((s) => ({
105
+ label: s.label,
106
+ distance: levenshteinDistance(
107
+ term.toLowerCase(),
108
+ s.label.toLowerCase()
109
+ )
110
+ })).sort((a, b) => a.distance - b.distance).slice(0, 5).map((s) => s.label);
111
+ const result2 = {
112
+ term,
113
+ found: false,
114
+ suggestions: ranked
115
+ };
116
+ this.cache?.set("mesh", term, result2);
117
+ return result2;
118
+ }
119
+ }
120
+ for (let len = firstWord.length - 1, iterations = 0; len >= 3 && iterations < 3; len--, iterations++) {
121
+ const truncated = firstWord.slice(0, len);
122
+ const truncResults = await this.fetchLookup(
123
+ truncated,
124
+ "startswith",
125
+ 25
126
+ );
127
+ const filtered = truncResults.filter(
128
+ (r) => r.label.toLowerCase().includes(restWordsPrefix)
129
+ );
130
+ if (filtered.length > 0) {
131
+ const ranked = filtered.map((s) => ({
132
+ label: s.label,
133
+ distance: levenshteinDistance(
134
+ term.toLowerCase(),
135
+ s.label.toLowerCase()
136
+ )
137
+ })).sort((a, b) => a.distance - b.distance).slice(0, 5).map((s) => s.label);
138
+ const result2 = {
139
+ term,
140
+ found: false,
141
+ suggestions: ranked
142
+ };
143
+ this.cache?.set("mesh", term, result2);
144
+ return result2;
145
+ }
146
+ }
147
+ const lastWord = words[words.length - 1];
148
+ const containsLastResults = await this.fetchLookup(
149
+ lastWord,
150
+ "contains",
151
+ 25
152
+ );
153
+ if (containsLastResults.length > 0) {
154
+ const ranked = containsLastResults.map((s) => ({
99
155
  label: s.label,
100
- distance: levenshteinDistance(term.toLowerCase(), s.label.toLowerCase())
156
+ distance: levenshteinDistance(
157
+ term.toLowerCase(),
158
+ s.label.toLowerCase()
159
+ )
101
160
  })).sort((a, b) => a.distance - b.distance).slice(0, 5).map((s) => s.label);
102
- const result2 = { term, found: false, suggestions: ranked };
161
+ const result2 = {
162
+ term,
163
+ found: false,
164
+ suggestions: ranked
165
+ };
103
166
  this.cache?.set("mesh", term, result2);
104
167
  return result2;
105
168
  }
@@ -1 +1 @@
1
- {"version":3,"file":"mesh-lookup.js","sources":["../../src/query/mesh-lookup.ts"],"sourcesContent":["/**\n * MeSH Lookup API client.\n *\n * Validates MeSH (Medical Subject Headings) terms against the NLM MeSH Lookup API.\n * No API key required.\n *\n * API docs: https://id.nlm.nih.gov/mesh/lookup/term\n */\n\nimport type { RateLimiter } from '../providers/base/rate-limiter.js';\nimport type { VocabCache } from './vocab-cache.js';\nimport { levenshteinDistance } from '../utils/levenshtein.js';\n\nconst MESH_LOOKUP_BASE_URL = 'https://id.nlm.nih.gov/mesh/lookup/term';\nconst DEFAULT_TIMEOUT_MS = 10_000;\n\n/**\n * Result of a MeSH term lookup.\n */\nexport interface MeSHLookupResult {\n /** The term that was looked up */\n term: string;\n /** Whether the term was found as a valid MeSH heading */\n found: boolean;\n /** Suggested terms if the lookup term was not found */\n suggestions?: string[];\n}\n\ninterface MeSHApiEntry {\n resource: string;\n label: string;\n}\n\n/**\n * Client for the NLM MeSH Lookup API.\n */\nexport class MeSHLookupClient {\n private readonly rateLimiter: RateLimiter | undefined;\n private readonly timeoutMs: number;\n private readonly cache: VocabCache | undefined;\n\n constructor(options?: { rateLimiter?: RateLimiter; timeoutMs?: number; cache?: VocabCache }) {\n this.rateLimiter = options?.rateLimiter;\n this.timeoutMs = options?.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n this.cache = options?.cache;\n }\n\n /**\n * Look up a single MeSH term.\n *\n * Tries multiple match strategies in order:\n * 1. exact — exact match\n * 2. startsWith (full term) — prefix match\n * 2b. startsWith (truncated) — suffix typo recovery (1-3 chars removed)\n * 2c. startsWith (word1 + word2 prefix) — multi-word progressive prefix (max 3 calls)\n * 3. contains (full term) — substring match\n * 4. startsWith (first word, limit=25) — re-ranked by Levenshtein distance\n *\n * Returns on the first strategy that produces results.\n * Results are cached when a VocabCache is provided.\n */\n async lookupTerm(term: string): Promise<MeSHLookupResult> {\n // Check cache first\n if (this.cache) {\n const cached = this.cache.get('mesh', term);\n if (cached) {\n return cached;\n }\n }\n\n // 1. Try exact match first\n const exactResults = await this.fetchLookup(term, 'exact', 1);\n\n if (exactResults.length > 0) {\n const result: MeSHLookupResult = { term, found: true };\n this.cache?.set('mesh', term, result);\n return result;\n }\n\n // 2. Try startsWith (full term) for suggestions\n const startsWithResults = await this.fetchLookup(term, 'startswith', 5);\n\n if (startsWithResults.length > 0) {\n const result: MeSHLookupResult = {\n term,\n found: false,\n suggestions: startsWithResults.map((s) => s.label),\n };\n this.cache?.set('mesh', term, result);\n return result;\n }\n\n // 2b. Try startsWith with progressively shorter input (handles suffix typos)\n if (term.length > 3) {\n for (let len = term.length - 1; len >= Math.max(term.length - 3, 3); len--) {\n const truncated = term.slice(0, len);\n const truncatedResults = await this.fetchLookup(truncated, 'startswith', 5);\n if (truncatedResults.length > 0) {\n const result: MeSHLookupResult = {\n term,\n found: false,\n suggestions: truncatedResults.map((s) => s.label),\n };\n this.cache?.set('mesh', term, result);\n return result;\n }\n }\n }\n\n // 2c. Multi-word progressive prefix: try word1 + word2.slice(0, N)\n const words = term.split(/\\s+/);\n if (words.length >= 2 && words[1]!.length > 3) {\n const startN = Math.min(words[1]!.length - 4, words[1]!.length - 1);\n const endN = 3;\n let iterations = 0;\n for (let n = startN; n >= endN && iterations < 3; n--, iterations++) {\n const prefix = words[0]! + ' ' + words[1]!.slice(0, n);\n const prefixResults = await this.fetchLookup(prefix, 'startswith', 5);\n if (prefixResults.length > 0) {\n const result: MeSHLookupResult = {\n term,\n found: false,\n suggestions: prefixResults.map((s) => s.label),\n };\n this.cache?.set('mesh', term, result);\n return result;\n }\n }\n }\n\n // 3. Try contains (full term) for typos and variant spellings\n const containsResults = await this.fetchLookup(term, 'contains', 5);\n\n if (containsResults.length > 0) {\n const result: MeSHLookupResult = {\n term,\n found: false,\n suggestions: containsResults.map((s) => s.label),\n };\n this.cache?.set('mesh', term, result);\n return result;\n }\n\n // 4. Try startsWith with first word only (for multi-word terms)\n // Fetch up to 25 results and re-rank by Levenshtein distance\n if (words.length > 1) {\n const firstWord = words[0]!;\n const firstWordResults = await this.fetchLookup(firstWord, 'startswith', 25);\n\n if (firstWordResults.length > 0) {\n const ranked = firstWordResults\n .map((s) => ({\n label: s.label,\n distance: levenshteinDistance(term.toLowerCase(), s.label.toLowerCase()),\n }))\n .sort((a, b) => a.distance - b.distance)\n .slice(0, 5)\n .map((s) => s.label);\n const result: MeSHLookupResult = { term, found: false, suggestions: ranked };\n this.cache?.set('mesh', term, result);\n return result;\n }\n }\n\n const result: MeSHLookupResult = { term, found: false };\n this.cache?.set('mesh', term, result);\n return result;\n }\n\n /**\n * Look up multiple MeSH terms.\n */\n async lookupTerms(terms: string[]): Promise<MeSHLookupResult[]> {\n const results: MeSHLookupResult[] = [];\n for (const term of terms) {\n results.push(await this.lookupTerm(term));\n }\n return results;\n }\n\n private async fetchLookup(\n label: string,\n match: 'exact' | 'startswith' | 'contains',\n limit: number\n ): Promise<MeSHApiEntry[]> {\n if (this.rateLimiter) {\n await this.rateLimiter.acquire();\n }\n\n const params = new URLSearchParams({\n label,\n match,\n limit: String(limit),\n });\n\n const url = `${MESH_LOOKUP_BASE_URL}?${params.toString()}`;\n\n let response: Response;\n try {\n response = await fetch(url, {\n signal: AbortSignal.timeout(this.timeoutMs),\n });\n } catch (error) {\n const message =\n error instanceof Error ? error.message : 'Unknown error';\n throw new Error(`MeSH lookup failed: ${message}`);\n }\n\n if (!response.ok) {\n throw new Error(\n `MeSH lookup failed: HTTP ${response.status} ${response.statusText}`\n );\n }\n\n return (await response.json()) as MeSHApiEntry[];\n }\n}\n"],"names":["result"],"mappings":"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"}
1
+ {"version":3,"file":"mesh-lookup.js","sources":["../../src/query/mesh-lookup.ts"],"sourcesContent":["/**\n * MeSH Lookup API client.\n *\n * Validates MeSH (Medical Subject Headings) terms against the NLM MeSH Lookup API.\n * No API key required.\n *\n * API docs: https://id.nlm.nih.gov/mesh/lookup/term\n */\n\nimport type { RateLimiter } from '../providers/base/rate-limiter.js';\nimport type { VocabCache } from './vocab-cache.js';\nimport { levenshteinDistance } from '../utils/levenshtein.js';\n\nconst MESH_LOOKUP_BASE_URL = 'https://id.nlm.nih.gov/mesh/lookup/term';\nconst DEFAULT_TIMEOUT_MS = 10_000;\n\n/**\n * Result of a MeSH term lookup.\n */\nexport interface MeSHLookupResult {\n /** The term that was looked up */\n term: string;\n /** Whether the term was found as a valid MeSH heading */\n found: boolean;\n /** Suggested terms if the lookup term was not found */\n suggestions?: string[];\n}\n\ninterface MeSHApiEntry {\n resource: string;\n label: string;\n}\n\n/**\n * Client for the NLM MeSH Lookup API.\n */\nexport class MeSHLookupClient {\n private readonly rateLimiter: RateLimiter | undefined;\n private readonly timeoutMs: number;\n private readonly cache: VocabCache | undefined;\n\n constructor(options?: { rateLimiter?: RateLimiter; timeoutMs?: number; cache?: VocabCache }) {\n this.rateLimiter = options?.rateLimiter;\n this.timeoutMs = options?.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n this.cache = options?.cache;\n }\n\n /**\n * Look up a single MeSH term.\n *\n * Tries multiple match strategies in order:\n * 1. exact — exact match\n * 2. startsWith (full term) — prefix match\n * 2b. startsWith (truncated) — suffix typo recovery (1-3 chars removed)\n * 2c. startsWith (word1 + word2 prefix) — multi-word progressive prefix (max 3 calls)\n * 3. contains (full term) — substring match\n * 4. startsWith (first word, limit=25) — re-ranked by Levenshtein distance\n *\n * Returns on the first strategy that produces results.\n * Results are cached when a VocabCache is provided.\n */\n async lookupTerm(term: string): Promise<MeSHLookupResult> {\n // Check cache first\n if (this.cache) {\n const cached = this.cache.get('mesh', term);\n if (cached) {\n return cached;\n }\n }\n\n // 1. Try exact match first\n const exactResults = await this.fetchLookup(term, 'exact', 1);\n\n if (exactResults.length > 0) {\n const result: MeSHLookupResult = { term, found: true };\n this.cache?.set('mesh', term, result);\n return result;\n }\n\n // 2. Try startsWith (full term) for suggestions\n const startsWithResults = await this.fetchLookup(term, 'startswith', 5);\n\n if (startsWithResults.length > 0) {\n const result: MeSHLookupResult = {\n term,\n found: false,\n suggestions: startsWithResults.map((s) => s.label),\n };\n this.cache?.set('mesh', term, result);\n return result;\n }\n\n // 2b. Try startsWith with progressively shorter input (handles suffix typos)\n if (term.length > 3) {\n for (let len = term.length - 1; len >= Math.max(term.length - 3, 3); len--) {\n const truncated = term.slice(0, len);\n const truncatedResults = await this.fetchLookup(truncated, 'startswith', 5);\n if (truncatedResults.length > 0) {\n const result: MeSHLookupResult = {\n term,\n found: false,\n suggestions: truncatedResults.map((s) => s.label),\n };\n this.cache?.set('mesh', term, result);\n return result;\n }\n }\n }\n\n // 2c. Multi-word progressive prefix: try word1 + word2.slice(0, N)\n const words = term.split(/\\s+/);\n if (words.length >= 2 && words[1]!.length > 3) {\n const startN = Math.min(words[1]!.length - 4, words[1]!.length - 1);\n const endN = 3;\n let iterations = 0;\n for (let n = startN; n >= endN && iterations < 3; n--, iterations++) {\n const prefix = words[0]! + ' ' + words[1]!.slice(0, n);\n const prefixResults = await this.fetchLookup(prefix, 'startswith', 5);\n if (prefixResults.length > 0) {\n const result: MeSHLookupResult = {\n term,\n found: false,\n suggestions: prefixResults.map((s) => s.label),\n };\n this.cache?.set('mesh', term, result);\n return result;\n }\n }\n }\n\n // 3. Try contains (full term) for typos and variant spellings\n const containsResults = await this.fetchLookup(term, 'contains', 5);\n\n if (containsResults.length > 0) {\n const result: MeSHLookupResult = {\n term,\n found: false,\n suggestions: containsResults.map((s) => s.label),\n };\n this.cache?.set('mesh', term, result);\n return result;\n }\n\n // 4. Try startsWith with first word only (for multi-word terms)\n // Fetch up to 25 results, filter by rest words, and re-rank by Levenshtein distance\n if (words.length > 1) {\n const firstWord = words[0]!;\n const restWords = words.slice(1).join(' ').toLowerCase();\n const restWordsPrefix = restWords.slice(0, 4);\n const firstWordResults = await this.fetchLookup(firstWord, 'startswith', 25);\n\n if (firstWordResults.length > 0) {\n // Filter by rest words when prefix is long enough to be meaningful\n const candidates =\n restWordsPrefix.length >= 4\n ? firstWordResults.filter((r) =>\n r.label.toLowerCase().includes(restWordsPrefix)\n )\n : firstWordResults;\n\n if (candidates.length > 0) {\n const ranked = candidates\n .map((s) => ({\n label: s.label,\n distance: levenshteinDistance(\n term.toLowerCase(),\n s.label.toLowerCase()\n ),\n }))\n .sort((a, b) => a.distance - b.distance)\n .slice(0, 5)\n .map((s) => s.label);\n const result: MeSHLookupResult = {\n term,\n found: false,\n suggestions: ranked,\n };\n this.cache?.set('mesh', term, result);\n return result;\n }\n // No relevant results after filtering — fall through to step 4b\n }\n\n // 4b. Truncated first word startsWith + rest word filter\n // Handle first-word typos by progressively shortening the first word\n for (\n let len = firstWord.length - 1, iterations = 0;\n len >= 3 && iterations < 3;\n len--, iterations++\n ) {\n const truncated = firstWord.slice(0, len);\n const truncResults = await this.fetchLookup(\n truncated,\n 'startswith',\n 25\n );\n const filtered = truncResults.filter((r) =>\n r.label.toLowerCase().includes(restWordsPrefix)\n );\n if (filtered.length > 0) {\n const ranked = filtered\n .map((s) => ({\n label: s.label,\n distance: levenshteinDistance(\n term.toLowerCase(),\n s.label.toLowerCase()\n ),\n }))\n .sort((a, b) => a.distance - b.distance)\n .slice(0, 5)\n .map((s) => s.label);\n const result: MeSHLookupResult = {\n term,\n found: false,\n suggestions: ranked,\n };\n this.cache?.set('mesh', term, result);\n return result;\n }\n }\n\n // 4c. Contains last word + Levenshtein re-ranking\n // Final fallback for severely misspelled first words\n const lastWord = words[words.length - 1]!;\n const containsLastResults = await this.fetchLookup(\n lastWord,\n 'contains',\n 25\n );\n if (containsLastResults.length > 0) {\n const ranked = containsLastResults\n .map((s) => ({\n label: s.label,\n distance: levenshteinDistance(\n term.toLowerCase(),\n s.label.toLowerCase()\n ),\n }))\n .sort((a, b) => a.distance - b.distance)\n .slice(0, 5)\n .map((s) => s.label);\n const result: MeSHLookupResult = {\n term,\n found: false,\n suggestions: ranked,\n };\n this.cache?.set('mesh', term, result);\n return result;\n }\n }\n\n const result: MeSHLookupResult = { term, found: false };\n this.cache?.set('mesh', term, result);\n return result;\n }\n\n /**\n * Look up multiple MeSH terms.\n */\n async lookupTerms(terms: string[]): Promise<MeSHLookupResult[]> {\n const results: MeSHLookupResult[] = [];\n for (const term of terms) {\n results.push(await this.lookupTerm(term));\n }\n return results;\n }\n\n private async fetchLookup(\n label: string,\n match: 'exact' | 'startswith' | 'contains',\n limit: number\n ): Promise<MeSHApiEntry[]> {\n if (this.rateLimiter) {\n await this.rateLimiter.acquire();\n }\n\n const params = new URLSearchParams({\n label,\n match,\n limit: String(limit),\n });\n\n const url = `${MESH_LOOKUP_BASE_URL}?${params.toString()}`;\n\n let response: Response;\n try {\n response = await fetch(url, {\n signal: AbortSignal.timeout(this.timeoutMs),\n });\n } catch (error) {\n const message =\n error instanceof Error ? error.message : 'Unknown error';\n throw new Error(`MeSH lookup failed: ${message}`);\n }\n\n if (!response.ok) {\n throw new Error(\n `MeSH lookup failed: HTTP ${response.status} ${response.statusText}`\n );\n }\n\n return (await response.json()) as MeSHApiEntry[];\n }\n}\n"],"names":["result"],"mappings":";AAaA,MAAM,uBAAuB;AAC7B,MAAM,qBAAqB;AAsBpB,MAAM,iBAAiB;AAAA,EACX;AAAA,EACA;AAAA,EACA;AAAA,EAEjB,YAAY,SAAiF;AAC3F,SAAK,cAAc,SAAS;AAC5B,SAAK,YAAY,SAAS,aAAa;AACvC,SAAK,QAAQ,SAAS;AAAA,EACxB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAgBA,MAAM,WAAW,MAAyC;AAExD,QAAI,KAAK,OAAO;AACd,YAAM,SAAS,KAAK,MAAM,IAAI,QAAQ,IAAI;AAC1C,UAAI,QAAQ;AACV,eAAO;AAAA,MACT;AAAA,IACF;AAGA,UAAM,eAAe,MAAM,KAAK,YAAY,MAAM,SAAS,CAAC;AAE5D,QAAI,aAAa,SAAS,GAAG;AAC3B,YAAMA,UAA2B,EAAE,MAAM,OAAO,KAAA;AAChD,WAAK,OAAO,IAAI,QAAQ,MAAMA,OAAM;AACpC,aAAOA;AAAAA,IACT;AAGA,UAAM,oBAAoB,MAAM,KAAK,YAAY,MAAM,cAAc,CAAC;AAEtE,QAAI,kBAAkB,SAAS,GAAG;AAChC,YAAMA,UAA2B;AAAA,QAC/B;AAAA,QACA,OAAO;AAAA,QACP,aAAa,kBAAkB,IAAI,CAAC,MAAM,EAAE,KAAK;AAAA,MAAA;AAEnD,WAAK,OAAO,IAAI,QAAQ,MAAMA,OAAM;AACpC,aAAOA;AAAAA,IACT;AAGA,QAAI,KAAK,SAAS,GAAG;AACnB,eAAS,MAAM,KAAK,SAAS,GAAG,OAAO,KAAK,IAAI,KAAK,SAAS,GAAG,CAAC,GAAG,OAAO;AAC1E,cAAM,YAAY,KAAK,MAAM,GAAG,GAAG;AACnC,cAAM,mBAAmB,MAAM,KAAK,YAAY,WAAW,cAAc,CAAC;AAC1E,YAAI,iBAAiB,SAAS,GAAG;AAC/B,gBAAMA,UAA2B;AAAA,YAC/B;AAAA,YACA,OAAO;AAAA,YACP,aAAa,iBAAiB,IAAI,CAAC,MAAM,EAAE,KAAK;AAAA,UAAA;AAElD,eAAK,OAAO,IAAI,QAAQ,MAAMA,OAAM;AACpC,iBAAOA;AAAAA,QACT;AAAA,MACF;AAAA,IACF;AAGA,UAAM,QAAQ,KAAK,MAAM,KAAK;AAC9B,QAAI,MAAM,UAAU,KAAK,MAAM,CAAC,EAAG,SAAS,GAAG;AAC7C,YAAM,SAAS,KAAK,IAAI,MAAM,CAAC,EAAG,SAAS,GAAG,MAAM,CAAC,EAAG,SAAS,CAAC;AAClE,YAAM,OAAO;AACb,UAAI,aAAa;AACjB,eAAS,IAAI,QAAQ,KAAK,QAAQ,aAAa,GAAG,KAAK,cAAc;AACnE,cAAM,SAAS,MAAM,CAAC,IAAK,MAAM,MAAM,CAAC,EAAG,MAAM,GAAG,CAAC;AACrD,cAAM,gBAAgB,MAAM,KAAK,YAAY,QAAQ,cAAc,CAAC;AACpE,YAAI,cAAc,SAAS,GAAG;AAC5B,gBAAMA,UAA2B;AAAA,YAC/B;AAAA,YACA,OAAO;AAAA,YACP,aAAa,cAAc,IAAI,CAAC,MAAM,EAAE,KAAK;AAAA,UAAA;AAE/C,eAAK,OAAO,IAAI,QAAQ,MAAMA,OAAM;AACpC,iBAAOA;AAAAA,QACT;AAAA,MACF;AAAA,IACF;AAGA,UAAM,kBAAkB,MAAM,KAAK,YAAY,MAAM,YAAY,CAAC;AAElE,QAAI,gBAAgB,SAAS,GAAG;AAC9B,YAAMA,UAA2B;AAAA,QAC/B;AAAA,QACA,OAAO;AAAA,QACP,aAAa,gBAAgB,IAAI,CAAC,MAAM,EAAE,KAAK;AAAA,MAAA;AAEjD,WAAK,OAAO,IAAI,QAAQ,MAAMA,OAAM;AACpC,aAAOA;AAAAA,IACT;AAIA,QAAI,MAAM,SAAS,GAAG;AACpB,YAAM,YAAY,MAAM,CAAC;AACzB,YAAM,YAAY,MAAM,MAAM,CAAC,EAAE,KAAK,GAAG,EAAE,YAAA;AAC3C,YAAM,kBAAkB,UAAU,MAAM,GAAG,CAAC;AAC5C,YAAM,mBAAmB,MAAM,KAAK,YAAY,WAAW,cAAc,EAAE;AAE3E,UAAI,iBAAiB,SAAS,GAAG;AAE/B,cAAM,aACJ,gBAAgB,UAAU,IACtB,iBAAiB;AAAA,UAAO,CAAC,MACvB,EAAE,MAAM,YAAA,EAAc,SAAS,eAAe;AAAA,QAAA,IAEhD;AAEN,YAAI,WAAW,SAAS,GAAG;AACzB,gBAAM,SAAS,WACZ,IAAI,CAAC,OAAO;AAAA,YACX,OAAO,EAAE;AAAA,YACT,UAAU;AAAA,cACR,KAAK,YAAA;AAAA,cACL,EAAE,MAAM,YAAA;AAAA,YAAY;AAAA,UACtB,EACA,EACD,KAAK,CAAC,GAAG,MAAM,EAAE,WAAW,EAAE,QAAQ,EACtC,MAAM,GAAG,CAAC,EACV,IAAI,CAAC,MAAM,EAAE,KAAK;AACrB,gBAAMA,UAA2B;AAAA,YAC/B;AAAA,YACA,OAAO;AAAA,YACP,aAAa;AAAA,UAAA;AAEf,eAAK,OAAO,IAAI,QAAQ,MAAMA,OAAM;AACpC,iBAAOA;AAAAA,QACT;AAAA,MAEF;AAIA,eACM,MAAM,UAAU,SAAS,GAAG,aAAa,GAC7C,OAAO,KAAK,aAAa,GACzB,OAAO,cACP;AACA,cAAM,YAAY,UAAU,MAAM,GAAG,GAAG;AACxC,cAAM,eAAe,MAAM,KAAK;AAAA,UAC9B;AAAA,UACA;AAAA,UACA;AAAA,QAAA;AAEF,cAAM,WAAW,aAAa;AAAA,UAAO,CAAC,MACpC,EAAE,MAAM,YAAA,EAAc,SAAS,eAAe;AAAA,QAAA;AAEhD,YAAI,SAAS,SAAS,GAAG;AACvB,gBAAM,SAAS,SACZ,IAAI,CAAC,OAAO;AAAA,YACX,OAAO,EAAE;AAAA,YACT,UAAU;AAAA,cACR,KAAK,YAAA;AAAA,cACL,EAAE,MAAM,YAAA;AAAA,YAAY;AAAA,UACtB,EACA,EACD,KAAK,CAAC,GAAG,MAAM,EAAE,WAAW,EAAE,QAAQ,EACtC,MAAM,GAAG,CAAC,EACV,IAAI,CAAC,MAAM,EAAE,KAAK;AACrB,gBAAMA,UAA2B;AAAA,YAC/B;AAAA,YACA,OAAO;AAAA,YACP,aAAa;AAAA,UAAA;AAEf,eAAK,OAAO,IAAI,QAAQ,MAAMA,OAAM;AACpC,iBAAOA;AAAAA,QACT;AAAA,MACF;AAIA,YAAM,WAAW,MAAM,MAAM,SAAS,CAAC;AACvC,YAAM,sBAAsB,MAAM,KAAK;AAAA,QACrC;AAAA,QACA;AAAA,QACA;AAAA,MAAA;AAEF,UAAI,oBAAoB,SAAS,GAAG;AAClC,cAAM,SAAS,oBACZ,IAAI,CAAC,OAAO;AAAA,UACX,OAAO,EAAE;AAAA,UACT,UAAU;AAAA,YACR,KAAK,YAAA;AAAA,YACL,EAAE,MAAM,YAAA;AAAA,UAAY;AAAA,QACtB,EACA,EACD,KAAK,CAAC,GAAG,MAAM,EAAE,WAAW,EAAE,QAAQ,EACtC,MAAM,GAAG,CAAC,EACV,IAAI,CAAC,MAAM,EAAE,KAAK;AACrB,cAAMA,UAA2B;AAAA,UAC/B;AAAA,UACA,OAAO;AAAA,UACP,aAAa;AAAA,QAAA;AAEf,aAAK,OAAO,IAAI,QAAQ,MAAMA,OAAM;AACpC,eAAOA;AAAAA,MACT;AAAA,IACF;AAEA,UAAM,SAA2B,EAAE,MAAM,OAAO,MAAA;AAChD,SAAK,OAAO,IAAI,QAAQ,MAAM,MAAM;AACpC,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,YAAY,OAA8C;AAC9D,UAAM,UAA8B,CAAA;AACpC,eAAW,QAAQ,OAAO;AACxB,cAAQ,KAAK,MAAM,KAAK,WAAW,IAAI,CAAC;AAAA,IAC1C;AACA,WAAO;AAAA,EACT;AAAA,EAEA,MAAc,YACZ,OACA,OACA,OACyB;AACzB,QAAI,KAAK,aAAa;AACpB,YAAM,KAAK,YAAY,QAAA;AAAA,IACzB;AAEA,UAAM,SAAS,IAAI,gBAAgB;AAAA,MACjC;AAAA,MACA;AAAA,MACA,OAAO,OAAO,KAAK;AAAA,IAAA,CACpB;AAED,UAAM,MAAM,GAAG,oBAAoB,IAAI,OAAO,UAAU;AAExD,QAAI;AACJ,QAAI;AACF,iBAAW,MAAM,MAAM,KAAK;AAAA,QAC1B,QAAQ,YAAY,QAAQ,KAAK,SAAS;AAAA,MAAA,CAC3C;AAAA,IACH,SAAS,OAAO;AACd,YAAM,UACJ,iBAAiB,QAAQ,MAAM,UAAU;AAC3C,YAAM,IAAI,MAAM,uBAAuB,OAAO,EAAE;AAAA,IAClD;AAEA,QAAI,CAAC,SAAS,IAAI;AAChB,YAAM,IAAI;AAAA,QACR,4BAA4B,SAAS,MAAM,IAAI,SAAS,UAAU;AAAA,MAAA;AAAA,IAEtE;AAEA,WAAQ,MAAM,SAAS,KAAA;AAAA,EACzB;AACF;"}
@@ -0,0 +1,14 @@
1
+ import { ProviderName, QueryAST, ResolvedAST } from './types.js';
2
+ /**
3
+ * Resolve a QueryAST for a specific provider.
4
+ *
5
+ * Applies provider-specific block replacements and filter additions,
6
+ * returning a flat ResolvedAST with no provider sections.
7
+ *
8
+ * @param ast - The full QueryAST with optional providers section
9
+ * @param provider - The target provider name
10
+ * @returns A ResolvedAST with replacements applied and filters merged
11
+ * @throws Error if replaces references a non-existent block id
12
+ */
13
+ export declare function resolveForProvider(ast: QueryAST, provider: ProviderName): ResolvedAST;
14
+ //# sourceMappingURL=resolver.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolver.d.ts","sourceRoot":"","sources":["../../src/query/resolver.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAW,YAAY,EAAE,QAAQ,EAAc,WAAW,EAAE,MAAM,YAAY,CAAC;AA+B3F;;;;;;;;;;GAUG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,QAAQ,EAAE,QAAQ,EAAE,YAAY,GAAG,WAAW,CAgDrF"}
@@ -0,0 +1,61 @@
1
+ function deepMergeFilters(base, override) {
2
+ const result = { ...base };
3
+ for (const [key, value] of Object.entries(override)) {
4
+ if (value === void 0) continue;
5
+ const k = key;
6
+ if (k === "publicationTypes" && typeof value === "object" && !Array.isArray(value)) {
7
+ result.publicationTypes = {
8
+ ...base.publicationTypes,
9
+ ...value
10
+ };
11
+ } else {
12
+ result[k] = value;
13
+ }
14
+ }
15
+ return result;
16
+ }
17
+ function resolveForProvider(ast, provider) {
18
+ const section = ast.providers[provider];
19
+ if (!section) {
20
+ return {
21
+ name: ast.name,
22
+ description: ast.description,
23
+ blocks: ast.blocks.map((b) => structuredClone(b)),
24
+ filters: structuredClone(ast.filters)
25
+ };
26
+ }
27
+ let blocks;
28
+ if (section.replaces) {
29
+ const blockIds = new Set(ast.blocks.map((b) => b.id));
30
+ for (const key of Object.keys(section.replaces)) {
31
+ if (!blockIds.has(key)) {
32
+ throw new Error(`replaces references non-existent block id: "${key}"`);
33
+ }
34
+ }
35
+ blocks = ast.blocks.map((block) => {
36
+ const replacement = section.replaces?.[block.id];
37
+ if (replacement) {
38
+ return { id: block.id, ...structuredClone(replacement) };
39
+ }
40
+ return structuredClone(block);
41
+ });
42
+ } else {
43
+ blocks = ast.blocks.map((b) => structuredClone(b));
44
+ }
45
+ let filters;
46
+ if (section.adds?.filters) {
47
+ filters = deepMergeFilters(ast.filters, section.adds.filters);
48
+ } else {
49
+ filters = structuredClone(ast.filters);
50
+ }
51
+ return {
52
+ name: ast.name,
53
+ description: ast.description,
54
+ blocks,
55
+ filters
56
+ };
57
+ }
58
+ export {
59
+ resolveForProvider
60
+ };
61
+ //# sourceMappingURL=resolver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolver.js","sources":["../../src/query/resolver.ts"],"sourcesContent":["/**\n * Query Resolution Layer\n *\n * Resolves provider-specific blocks and filters from a QueryAST,\n * producing a flat ResolvedAST suitable for translation.\n */\n\nimport type { Filters, ProviderName, QueryAST, QueryBlock, ResolvedAST } from './types.js';\n\n/**\n * Deep-merge override filters into base filters.\n * - Scalars: override replaces base\n * - Arrays: override replaces base\n * - Objects (publicationTypes): deep-merge recursively\n */\nfunction deepMergeFilters(base: Filters, override: Partial<Filters>): Filters {\n const result: Filters = { ...base };\n\n for (const [key, value] of Object.entries(override)) {\n if (value === undefined) continue;\n\n const k = key as keyof Filters;\n\n if (k === 'publicationTypes' && typeof value === 'object' && !Array.isArray(value)) {\n // Deep-merge publicationTypes\n result.publicationTypes = {\n ...base.publicationTypes,\n ...value,\n };\n } else {\n // Scalars and arrays: override replaces\n (result as Record<string, unknown>)[k] = value;\n }\n }\n\n return result;\n}\n\n/**\n * Resolve a QueryAST for a specific provider.\n *\n * Applies provider-specific block replacements and filter additions,\n * returning a flat ResolvedAST with no provider sections.\n *\n * @param ast - The full QueryAST with optional providers section\n * @param provider - The target provider name\n * @returns A ResolvedAST with replacements applied and filters merged\n * @throws Error if replaces references a non-existent block id\n */\nexport function resolveForProvider(ast: QueryAST, provider: ProviderName): ResolvedAST {\n const section = ast.providers[provider];\n\n // No provider section — return defaults\n if (!section) {\n return {\n name: ast.name,\n description: ast.description,\n blocks: ast.blocks.map((b) => structuredClone(b)),\n filters: structuredClone(ast.filters),\n };\n }\n\n // Apply block replacements\n let blocks: QueryBlock[];\n if (section.replaces) {\n const blockIds = new Set(ast.blocks.map((b) => b.id));\n for (const key of Object.keys(section.replaces)) {\n if (!blockIds.has(key)) {\n throw new Error(`replaces references non-existent block id: \"${key}\"`);\n }\n }\n\n blocks = ast.blocks.map((block) => {\n const replacement = section.replaces?.[block.id];\n if (replacement) {\n return { id: block.id, ...structuredClone(replacement) };\n }\n return structuredClone(block);\n });\n } else {\n blocks = ast.blocks.map((b) => structuredClone(b));\n }\n\n // Apply filter additions\n let filters: Filters;\n if (section.adds?.filters) {\n filters = deepMergeFilters(ast.filters, section.adds.filters);\n } else {\n filters = structuredClone(ast.filters);\n }\n\n return {\n name: ast.name,\n description: ast.description,\n blocks,\n filters,\n };\n}\n"],"names":[],"mappings":"AAeA,SAAS,iBAAiB,MAAe,UAAqC;AAC5E,QAAM,SAAkB,EAAE,GAAG,KAAA;AAE7B,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,QAAQ,GAAG;AACnD,QAAI,UAAU,OAAW;AAEzB,UAAM,IAAI;AAEV,QAAI,MAAM,sBAAsB,OAAO,UAAU,YAAY,CAAC,MAAM,QAAQ,KAAK,GAAG;AAElF,aAAO,mBAAmB;AAAA,QACxB,GAAG,KAAK;AAAA,QACR,GAAG;AAAA,MAAA;AAAA,IAEP,OAAO;AAEJ,aAAmC,CAAC,IAAI;AAAA,IAC3C;AAAA,EACF;AAEA,SAAO;AACT;AAaO,SAAS,mBAAmB,KAAe,UAAqC;AACrF,QAAM,UAAU,IAAI,UAAU,QAAQ;AAGtC,MAAI,CAAC,SAAS;AACZ,WAAO;AAAA,MACL,MAAM,IAAI;AAAA,MACV,aAAa,IAAI;AAAA,MACjB,QAAQ,IAAI,OAAO,IAAI,CAAC,MAAM,gBAAgB,CAAC,CAAC;AAAA,MAChD,SAAS,gBAAgB,IAAI,OAAO;AAAA,IAAA;AAAA,EAExC;AAGA,MAAI;AACJ,MAAI,QAAQ,UAAU;AACpB,UAAM,WAAW,IAAI,IAAI,IAAI,OAAO,IAAI,CAAC,MAAM,EAAE,EAAE,CAAC;AACpD,eAAW,OAAO,OAAO,KAAK,QAAQ,QAAQ,GAAG;AAC/C,UAAI,CAAC,SAAS,IAAI,GAAG,GAAG;AACtB,cAAM,IAAI,MAAM,+CAA+C,GAAG,GAAG;AAAA,MACvE;AAAA,IACF;AAEA,aAAS,IAAI,OAAO,IAAI,CAAC,UAAU;AACjC,YAAM,cAAc,QAAQ,WAAW,MAAM,EAAE;AAC/C,UAAI,aAAa;AACf,eAAO,EAAE,IAAI,MAAM,IAAI,GAAG,gBAAgB,WAAW,EAAA;AAAA,MACvD;AACA,aAAO,gBAAgB,KAAK;AAAA,IAC9B,CAAC;AAAA,EACH,OAAO;AACL,aAAS,IAAI,OAAO,IAAI,CAAC,MAAM,gBAAgB,CAAC,CAAC;AAAA,EACnD;AAGA,MAAI;AACJ,MAAI,QAAQ,MAAM,SAAS;AACzB,cAAU,iBAAiB,IAAI,SAAS,QAAQ,KAAK,OAAO;AAAA,EAC9D,OAAO;AACL,cAAU,gBAAgB,IAAI,OAAO;AAAA,EACvC;AAEA,SAAO;AAAA,IACL,MAAM,IAAI;AAAA,IACV,aAAa,IAAI;AAAA,IACjB;AAAA,IACA;AAAA,EAAA;AAEJ;"}
@@ -29,6 +29,8 @@ export interface TermBlock {
29
29
  * A single query block targeting a specific field.
30
30
  */
31
31
  export interface QueryBlock {
32
+ /** Unique identifier for cross-referencing in provider sections */
33
+ id: string;
32
34
  /** Target field for the search */
33
35
  field: FieldType;
34
36
  /** Search terms */
@@ -57,18 +59,21 @@ export interface Filters {
57
59
  languages?: string[] | undefined;
58
60
  /** Publication type filters */
59
61
  publicationTypes?: PublicationTypeFilter | undefined;
62
+ /** arXiv categories */
63
+ categories?: string[] | undefined;
64
+ /** Scopus source types */
65
+ sourceTypes?: string[] | undefined;
60
66
  }
61
67
  /**
62
- * Database-specific override block.
63
- * Allows customization of filters and database-specific options.
68
+ * Provider-specific section with block replacements and filter additions.
64
69
  */
65
- export interface OverrideBlock {
66
- /** Override global filters for this provider */
67
- filters?: Filters | undefined;
68
- /** arXiv categories (arXiv only) */
69
- categories?: string[] | undefined;
70
- /** Source types (Scopus only) */
71
- sourceTypes?: string[] | undefined;
70
+ export interface ProviderSection {
71
+ /** Block replacements keyed by block id */
72
+ replaces?: Record<string, Omit<QueryBlock, 'id'>> | undefined;
73
+ /** Additional filters to merge with defaults */
74
+ adds?: {
75
+ filters?: Partial<Filters> | undefined;
76
+ } | undefined;
72
77
  }
73
78
  /**
74
79
  * Complete Query Abstract Syntax Tree.
@@ -83,7 +88,22 @@ export interface QueryAST {
83
88
  blocks: QueryBlock[];
84
89
  /** Global filters */
85
90
  filters: Filters;
86
- /** Provider-specific overrides */
87
- overrides: Partial<Record<ProviderName, OverrideBlock | undefined>>;
91
+ /** Provider-specific sections (defaults to {} when parsed from YAML) */
92
+ providers: Partial<Record<ProviderName, ProviderSection | undefined>>;
93
+ }
94
+ /**
95
+ * Resolved AST — output of resolveForProvider.
96
+ * Contains blocks with replacements applied and filters merged.
97
+ * No provider-specific sections remain.
98
+ */
99
+ export interface ResolvedAST {
100
+ /** Query identifier */
101
+ name: string;
102
+ /** Human-readable description */
103
+ description?: string | undefined;
104
+ /** Query blocks with provider replacements applied */
105
+ blocks: QueryBlock[];
106
+ /** Filters with provider additions merged */
107
+ filters: Filters;
88
108
  }
89
109
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/query/types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAC/D,YAAY,EAAE,YAAY,EAAE,CAAC;AAE7B;;;GAGG;AACH,MAAM,MAAM,SAAS,GACjB,OAAO,GACP,UAAU,GACV,gBAAgB,GAChB,QAAQ,GACR,SAAS,GACT,KAAK,CAAC;AAEV;;GAEG;AACH,MAAM,MAAM,QAAQ,GAAG,KAAK,GAAG,IAAI,CAAC;AAEpC;;;GAGG;AACH,MAAM,WAAW,SAAS;IACxB,sDAAsD;IACtD,QAAQ,CAAC,EAAE,MAAM,EAAE,GAAG,SAAS,CAAC;IAChC,+BAA+B;IAC/B,IAAI,CAAC,EAAE,MAAM,EAAE,GAAG,SAAS,CAAC;IAC5B,mCAAmC;IACnC,MAAM,CAAC,EAAE,MAAM,EAAE,GAAG,SAAS,CAAC;IAC9B,mCAAmC;IACnC,IAAI,CAAC,EAAE,MAAM,EAAE,GAAG,SAAS,CAAC;IAC5B,0DAA0D;IAC1D,OAAO,CAAC,EAAE,MAAM,EAAE,GAAG,SAAS,CAAC;CAChC;AAED;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,kCAAkC;IAClC,KAAK,EAAE,SAAS,CAAC;IACjB,mBAAmB;IACnB,KAAK,EAAE,SAAS,CAAC;IACjB,6CAA6C;IAC7C,QAAQ,EAAE,QAAQ,CAAC;CACpB;AAED;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,mCAAmC;IACnC,OAAO,CAAC,EAAE,MAAM,EAAE,GAAG,SAAS,CAAC;IAC/B,mCAAmC;IACnC,OAAO,CAAC,EAAE,MAAM,EAAE,GAAG,SAAS,CAAC;CAChC;AAED;;GAEG;AACH,MAAM,WAAW,OAAO;IACtB,uCAAuC;IACvC,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9B,qCAAqC;IACrC,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5B,wCAAwC;IACxC,SAAS,CAAC,EAAE,MAAM,EAAE,GAAG,SAAS,CAAC;IACjC,+BAA+B;IAC/B,gBAAgB,CAAC,EAAE,qBAAqB,GAAG,SAAS,CAAC;CACtD;AAED;;;GAGG;AACH,MAAM,WAAW,aAAa;IAC5B,gDAAgD;IAChD,OAAO,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC9B,oCAAoC;IACpC,UAAU,CAAC,EAAE,MAAM,EAAE,GAAG,SAAS,CAAC;IAClC,iCAAiC;IACjC,WAAW,CAAC,EAAE,MAAM,EAAE,GAAG,SAAS,CAAC;CACpC;AAED;;;GAGG;AACH,MAAM,WAAW,QAAQ;IACvB,uBAAuB;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,iCAAiC;IACjC,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC,4CAA4C;IAC5C,MAAM,EAAE,UAAU,EAAE,CAAC;IACrB,qBAAqB;IACrB,OAAO,EAAE,OAAO,CAAC;IACjB,kCAAkC;IAClC,SAAS,EAAE,OAAO,CAAC,MAAM,CAAC,YAAY,EAAE,aAAa,GAAG,SAAS,CAAC,CAAC,CAAC;CACrE"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/query/types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAC/D,YAAY,EAAE,YAAY,EAAE,CAAC;AAE7B;;;GAGG;AACH,MAAM,MAAM,SAAS,GACjB,OAAO,GACP,UAAU,GACV,gBAAgB,GAChB,QAAQ,GACR,SAAS,GACT,KAAK,CAAC;AAEV;;GAEG;AACH,MAAM,MAAM,QAAQ,GAAG,KAAK,GAAG,IAAI,CAAC;AAEpC;;;GAGG;AACH,MAAM,WAAW,SAAS;IACxB,sDAAsD;IACtD,QAAQ,CAAC,EAAE,MAAM,EAAE,GAAG,SAAS,CAAC;IAChC,+BAA+B;IAC/B,IAAI,CAAC,EAAE,MAAM,EAAE,GAAG,SAAS,CAAC;IAC5B,mCAAmC;IACnC,MAAM,CAAC,EAAE,MAAM,EAAE,GAAG,SAAS,CAAC;IAC9B,mCAAmC;IACnC,IAAI,CAAC,EAAE,MAAM,EAAE,GAAG,SAAS,CAAC;IAC5B,0DAA0D;IAC1D,OAAO,CAAC,EAAE,MAAM,EAAE,GAAG,SAAS,CAAC;CAChC;AAED;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,mEAAmE;IACnE,EAAE,EAAE,MAAM,CAAC;IACX,kCAAkC;IAClC,KAAK,EAAE,SAAS,CAAC;IACjB,mBAAmB;IACnB,KAAK,EAAE,SAAS,CAAC;IACjB,6CAA6C;IAC7C,QAAQ,EAAE,QAAQ,CAAC;CACpB;AAED;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,mCAAmC;IACnC,OAAO,CAAC,EAAE,MAAM,EAAE,GAAG,SAAS,CAAC;IAC/B,mCAAmC;IACnC,OAAO,CAAC,EAAE,MAAM,EAAE,GAAG,SAAS,CAAC;CAChC;AAED;;GAEG;AACH,MAAM,WAAW,OAAO;IACtB,uCAAuC;IACvC,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9B,qCAAqC;IACrC,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5B,wCAAwC;IACxC,SAAS,CAAC,EAAE,MAAM,EAAE,GAAG,SAAS,CAAC;IACjC,+BAA+B;IAC/B,gBAAgB,CAAC,EAAE,qBAAqB,GAAG,SAAS,CAAC;IACrD,uBAAuB;IACvB,UAAU,CAAC,EAAE,MAAM,EAAE,GAAG,SAAS,CAAC;IAClC,0BAA0B;IAC1B,WAAW,CAAC,EAAE,MAAM,EAAE,GAAG,SAAS,CAAC;CACpC;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,2CAA2C;IAC3C,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC,GAAG,SAAS,CAAC;IAC9D,gDAAgD;IAChD,IAAI,CAAC,EAAE;QACL,OAAO,CAAC,EAAE,OAAO,CAAC,OAAO,CAAC,GAAG,SAAS,CAAC;KACxC,GAAG,SAAS,CAAC;CACf;AAED;;;GAGG;AACH,MAAM,WAAW,QAAQ;IACvB,uBAAuB;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,iCAAiC;IACjC,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC,4CAA4C;IAC5C,MAAM,EAAE,UAAU,EAAE,CAAC;IACrB,qBAAqB;IACrB,OAAO,EAAE,OAAO,CAAC;IACjB,wEAAwE;IACxE,SAAS,EAAE,OAAO,CAAC,MAAM,CAAC,YAAY,EAAE,eAAe,GAAG,SAAS,CAAC,CAAC,CAAC;CACvE;AAED;;;;GAIG;AACH,MAAM,WAAW,WAAW;IAC1B,uBAAuB;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,iCAAiC;IACjC,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC,sDAAsD;IACtD,MAAM,EAAE,UAAU,EAAE,CAAC;IACrB,6CAA6C;IAC7C,OAAO,EAAE,OAAO,CAAC;CAClB"}