@cyanheads/openalex-mcp-server 0.10.0 → 0.11.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 (64) hide show
  1. package/AGENTS.md +30 -15
  2. package/CLAUDE.md +30 -15
  3. package/Dockerfile +6 -0
  4. package/README.md +55 -116
  5. package/changelog/0.10.x/0.10.1.md +28 -0
  6. package/changelog/0.11.x/0.11.0.md +41 -0
  7. package/dist/config/server-config.d.ts.map +1 -1
  8. package/dist/config/server-config.js +1 -1
  9. package/dist/config/server-config.js.map +1 -1
  10. package/dist/index.js +2 -1
  11. package/dist/index.js.map +1 -1
  12. package/dist/mcp-server/prompts/definitions/literature-review.prompt.js +1 -1
  13. package/dist/mcp-server/prompts/definitions/research-landscape.prompt.js +1 -1
  14. package/dist/mcp-server/tools/definitions/analyze-trends.tool.d.ts +1 -0
  15. package/dist/mcp-server/tools/definitions/analyze-trends.tool.d.ts.map +1 -1
  16. package/dist/mcp-server/tools/definitions/analyze-trends.tool.js +10 -5
  17. package/dist/mcp-server/tools/definitions/analyze-trends.tool.js.map +1 -1
  18. package/dist/mcp-server/tools/definitions/citation-graph.tool.d.ts +6 -9
  19. package/dist/mcp-server/tools/definitions/citation-graph.tool.d.ts.map +1 -1
  20. package/dist/mcp-server/tools/definitions/citation-graph.tool.js +43 -33
  21. package/dist/mcp-server/tools/definitions/citation-graph.tool.js.map +1 -1
  22. package/dist/mcp-server/tools/definitions/describe-fields.tool.d.ts +1 -0
  23. package/dist/mcp-server/tools/definitions/describe-fields.tool.d.ts.map +1 -1
  24. package/dist/mcp-server/tools/definitions/describe-fields.tool.js +25 -1
  25. package/dist/mcp-server/tools/definitions/describe-fields.tool.js.map +1 -1
  26. package/dist/mcp-server/tools/definitions/resolve-name.tool.d.ts.map +1 -1
  27. package/dist/mcp-server/tools/definitions/resolve-name.tool.js +44 -16
  28. package/dist/mcp-server/tools/definitions/resolve-name.tool.js.map +1 -1
  29. package/dist/mcp-server/tools/definitions/search-entities.tool.d.ts +22 -18
  30. package/dist/mcp-server/tools/definitions/search-entities.tool.d.ts.map +1 -1
  31. package/dist/mcp-server/tools/definitions/search-entities.tool.js +194 -85
  32. package/dist/mcp-server/tools/definitions/search-entities.tool.js.map +1 -1
  33. package/dist/mcp-server/tools/escape-markdown.d.ts +26 -11
  34. package/dist/mcp-server/tools/escape-markdown.d.ts.map +1 -1
  35. package/dist/mcp-server/tools/escape-markdown.js +95 -26
  36. package/dist/mcp-server/tools/escape-markdown.js.map +1 -1
  37. package/dist/mcp-server/tools/page-size.d.ts +38 -7
  38. package/dist/mcp-server/tools/page-size.d.ts.map +1 -1
  39. package/dist/mcp-server/tools/page-size.js +37 -11
  40. package/dist/mcp-server/tools/page-size.js.map +1 -1
  41. package/dist/mcp-server/tools/record-page.d.ts +174 -0
  42. package/dist/mcp-server/tools/record-page.d.ts.map +1 -0
  43. package/dist/mcp-server/tools/record-page.js +392 -0
  44. package/dist/mcp-server/tools/record-page.js.map +1 -0
  45. package/dist/mcp-server/tools/render-entity-record.d.ts +3 -1
  46. package/dist/mcp-server/tools/render-entity-record.d.ts.map +1 -1
  47. package/dist/mcp-server/tools/render-entity-record.js +7 -2
  48. package/dist/mcp-server/tools/render-entity-record.js.map +1 -1
  49. package/dist/services/openalex/field-catalog.json +107 -0
  50. package/dist/services/openalex/openalex-service.d.ts +18 -7
  51. package/dist/services/openalex/openalex-service.d.ts.map +1 -1
  52. package/dist/services/openalex/openalex-service.js +84 -63
  53. package/dist/services/openalex/openalex-service.js.map +1 -1
  54. package/dist/services/openalex/types.d.ts +13 -2
  55. package/dist/services/openalex/types.d.ts.map +1 -1
  56. package/dist/services/openalex/types.js +28 -1
  57. package/dist/services/openalex/types.js.map +1 -1
  58. package/package.json +7 -6
  59. package/server.json +5 -5
  60. package/skills/openalex-guide/SKILL.md +77 -0
  61. package/dist/mcp-server/tools/response-budget.d.ts +0 -159
  62. package/dist/mcp-server/tools/response-budget.d.ts.map +0 -1
  63. package/dist/mcp-server/tools/response-budget.js +0 -509
  64. package/dist/mcp-server/tools/response-budget.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"render-entity-record.js","sourceRoot":"","sources":["../../../src/mcp-server/tools/render-entity-record.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,cAAc,EAAE,MAAM,uCAAuC,CAAC;AAGvE,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC;IACvB,KAAK;IACL,KAAK;IACL,MAAM;IACN,IAAI;IACJ,MAAM;IACN,IAAI;IACJ,OAAO;IACP,OAAO;IACP,MAAM;IACN,KAAK;IACL,KAAK;CACN,CAAC,CAAC;AAIH,SAAS,YAAY,CAAC,KAAa;IACjC,OAAO,KAAK;SACT,KAAK,CAAC,SAAS,CAAC;SAChB,MAAM,CAAC,OAAO,CAAC;SACf,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CACZ,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC;QAC9B,CAAC,CAAC,IAAI,CAAC,WAAW,EAAE;QACpB,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CACjD;SACA,IAAI,CAAC,GAAG,CAAC,CAAC;AACf,CAAC;AAED,SAAS,QAAQ,CAAC,KAAc;IAC9B,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,OAAO,KAAK,KAAK,SAAS,CAAC;AAC9F,CAAC;AAED,SAAS,cAAc,CAAC,KAAc;IACpC,OAAO,KAAK,KAAK,IAAI,IAAI,QAAQ,CAAC,KAAK,CAAC,CAAC;AAC3C,CAAC;AAED,SAAS,aAAa,CAAC,KAAc;IACnC,OAAO,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;AAC9E,CAAC;AAED;;;;GAIG;AACH,SAAS,YAAY,CAAC,KAAa;IACjC,OAAO,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAC3E,CAAC;AAED,SAAS,gBAAgB,CAAC,MAAyB;IACjD,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACrF,CAAC;AAED;;;;;;GAMG;AACH,SAAS,aAAa,CAAC,KAAc,EAAE,MAAM,GAAG,EAAE;IAChD,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,MAAM,CAAC,CAAC,CAAC,CAAC,GAAG,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IACjF,IAAI,QAAQ,CAAC,KAAK,CAAC;QAAE,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,MAAM,KAAK,YAAY,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC,CAAC;IACjG,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACzB,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,CAAC,GAAG,MAAM,IAAI,OAAO,WAAW,CAAC,CAAC;QACjE,IAAI,KAAK,CAAC,KAAK,CAAC,cAAc,CAAC,EAAE,CAAC;YAChC,MAAM,MAAM,GAAG,gBAAgB,CAAC,KAAK,CAAC,CAAC;YACvC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,MAAM,KAAK,MAAM,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;QACpD,CAAC;QACD,OAAO,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,CAAC,EAAE,EAAE,CAAC,aAAa,CAAC,IAAI,EAAE,GAAG,MAAM,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;IAC5E,CAAC;IACD,IAAI,aAAa,CAAC,KAAK,CAAC,EAAE,CAAC;QACzB,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;QACtC,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,MAAM,CAAC,CAAC,CAAC,CAAC,GAAG,MAAM,WAAW,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QACtE,OAAO,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,aAAa,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,GAAG,MAAM,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACtF,CAAC;IACD,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,MAAM,KAAK,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;AAClE,CAAC;AAED,SAAS,YAAY,CAAC,KAA8B;IAClD,OAAO,aAAa,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACzC,CAAC;AAUD,MAAM,KAAK,GAAG,CAAC,CAAS,EAAU,EAAE,CAAC,CAAC,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC;AAE/D,sGAAsG;AACtG,SAAS,UAAU,CAAC,KAAa,EAAE,IAAiC;IAClE,IAAI,CAAC,IAAI,IAAI,KAAK,IAAI,IAAI,CAAC,KAAK;QAAE,OAAO;IACzC,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,gBAAgB,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IACzE,OAAO,IAAI,KAAK,CAAC,KAAK,CAAC,OAAO,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,SAAS,IAAI,0BAA0B,CAAC;AACzF,CAAC;AAED,SAAS,WAAW,CAAC,KAAa,EAAE,KAAc,EAAE,IAAiC;IACnF,MAAM,KAAK,GAAG,YAAY,CAAC,KAAK,CAAC,CAAC;IAClC,IAAI,KAAK,IAAI,IAAI;QAAE,OAAO,KAAK,KAAK,OAAO,CAAC;IAC5C,IAAI,QAAQ,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,KAAK,OAAO,YAAY,CAAC,KAAK,CAAC,EAAE,CAAC;IACnE,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACzB,MAAM,IAAI,GAAG,UAAU,CAAC,KAAK,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;QAC5C,MAAM,IAAI,GAAG,IAAI,CAAC,CAAC,CAAC,KAAK,KAAK,OAAO,IAAI,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,KAAK,CAAC;QAC9D,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,KAAK,aAAa,CAAC;QACrE,IAAI,KAAK,CAAC,KAAK,CAAC,cAAc,CAAC;YAAE,OAAO,GAAG,IAAI,IAAI,gBAAgB,CAAC,KAAK,CAAC,EAAE,CAAC;QAC7E,MAAM,KAAK,GAAG,IAAI,EAAE,MAAM,IAAI,CAAC,CAAC;QAChC,MAAM,KAAK,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,EAAE,EAAE,CAClC,aAAa,CAAC,IAAI,CAAC;YACjB,CAAC,CAAC,MAAM,KAAK,GAAG,CAAC,KAAK,YAAY,CAAC,IAAI,CAAC,EAAE;YAC1C,CAAC,CAAC,MAAM,KAAK,GAAG,CAAC,KAAK,aAAa,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,GAAG,EAAE,CAChE,CAAC;QACF,OAAO,GAAG,IAAI,KAAK,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;IACxC,CAAC;IACD,IAAI,aAAa,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,KAAK,OAAO,YAAY,CAAC,KAAK,CAAC,EAAE,CAAC;IACxE,OAAO,KAAK,KAAK,OAAO,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;AAC1C,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,kBAAkB,CAChC,MAAoB,EACpB,OAA8C;IAE9C,MAAM,EAAE,EAAE,EAAE,YAAY,EAAE,GAAG,IAAI,EAAE,GAAG,MAAM,CAAC;IAC7C,MAAM,KAAK,GAAG;QACZ,EAAE;QACF,OAAO,cAAc,CAAC,YAAY,IAAI,EAAE,EAAE,SAAS,CAAC,EAAE;QACtD,WAAW,cAAc,CAAC,EAAE,CAAC,EAAE;KAChC,CAAC;IACF,KAAK,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;QAClD,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IAC7D,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC"}
1
+ {"version":3,"file":"render-entity-record.js","sourceRoot":"","sources":["../../../src/mcp-server/tools/render-entity-record.ts"],"names":[],"mappings":"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"}
@@ -512,6 +512,8 @@
512
512
  "topics.subfield.id",
513
513
  "type",
514
514
  "works_count",
515
+ "works_count_with_children",
516
+ "works_count_with_children_and_predecessors",
515
517
  "x_concepts.id"
516
518
  ],
517
519
  "select": [
@@ -530,6 +532,8 @@
530
532
  "display_name_alternatives",
531
533
  "repositories",
532
534
  "works_count",
535
+ "works_count_with_children",
536
+ "works_count_with_children_and_predecessors",
533
537
  "cited_by_count",
534
538
  "summary_stats",
535
539
  "ids",
@@ -715,5 +719,108 @@
715
719
  "updated_date",
716
720
  "created_date"
717
721
  ]
722
+ },
723
+ "awards": {
724
+ "filter": [
725
+ "amount",
726
+ "amount_usd",
727
+ "amount_usd_basis",
728
+ "collection",
729
+ "currency",
730
+ "default.search",
731
+ "description.search",
732
+ "display_name.search",
733
+ "doi",
734
+ "end_year",
735
+ "funded_outputs",
736
+ "funded_outputs_count",
737
+ "funder.country_code",
738
+ "funder.doi",
739
+ "funder.id",
740
+ "funder.ror",
741
+ "funder_award_id",
742
+ "funder_name",
743
+ "funder_scheme",
744
+ "funding_type",
745
+ "has_parent_award",
746
+ "has_recipient_country",
747
+ "id",
748
+ "institution_awarded.continent",
749
+ "institution_awarded.country_code",
750
+ "institution_awarded.id",
751
+ "institution_awarded.lineage",
752
+ "institution_awarded.ror",
753
+ "institution_awarded.type",
754
+ "lead_investigator.affiliation.country",
755
+ "lead_investigator.affiliation.name",
756
+ "lead_investigator.family_name",
757
+ "lead_investigator.given_name",
758
+ "lead_investigator.orcid",
759
+ "parent_awards.funder.id",
760
+ "parent_awards.id",
761
+ "parent_awards.relationship",
762
+ "primary_topic.domain.id",
763
+ "primary_topic.field.id",
764
+ "primary_topic.id",
765
+ "primary_topic.subfield.id",
766
+ "provenance",
767
+ "recipient_countries.basis",
768
+ "recipient_countries.continent",
769
+ "recipient_countries.country_code",
770
+ "recipient_regions.code",
771
+ "recipients.id",
772
+ "recipients.ror",
773
+ "research_scope.basis",
774
+ "research_scope.is_research",
775
+ "start_year",
776
+ "sub_awards.funder.id",
777
+ "sub_awards.id",
778
+ "sub_awards.relationship",
779
+ "sub_awards_count",
780
+ "text.search",
781
+ "topics.domain.id",
782
+ "topics.field.id",
783
+ "topics.id",
784
+ "topics.subfield.id"
785
+ ],
786
+ "select": [
787
+ "id",
788
+ "display_name",
789
+ "description",
790
+ "funder_award_id",
791
+ "funder",
792
+ "funded_outputs",
793
+ "funded_outputs_count",
794
+ "parent_awards",
795
+ "sub_awards",
796
+ "sub_awards_count",
797
+ "amount",
798
+ "currency",
799
+ "amount_usd",
800
+ "amount_usd_basis",
801
+ "recipients",
802
+ "recipient_countries",
803
+ "recipient_regions",
804
+ "research_scope",
805
+ "funding_type",
806
+ "funder_scheme",
807
+ "start_date",
808
+ "end_date",
809
+ "start_year",
810
+ "end_year",
811
+ "landing_page_url",
812
+ "doi",
813
+ "provenance",
814
+ "lead_investigator",
815
+ "co_lead_investigator",
816
+ "investigators",
817
+ "works_api_url",
818
+ "primary_topic",
819
+ "topics",
820
+ "institution_awarded",
821
+ "updated_date",
822
+ "created_date",
823
+ "relevance_score"
824
+ ]
718
825
  }
719
826
  }
@@ -3,8 +3,19 @@
3
3
  * @module services/openalex/openalex-service
4
4
  */
5
5
  import type { Context } from '@cyanheads/mcp-ts-core';
6
- import { type AnalyzeParams, type AnalyzeResult, type AutocompleteParams, type AutocompleteRecord, type AutocompleteResult, type EntityRecord, type EntityType, type SearchParams, type SearchResult } from './types.js';
7
- /** Whether this aggregation keeps count order, ignoring the native unknown-bucket suffix. */
6
+ import { type AnalyzeParams, type AnalyzeResult, type AutocompleteEntityType, type AutocompleteParams, type AutocompleteRecord, type AutocompleteResult, type EntityRecord, type EntityType, type SearchParams, type SearchResult } from './types.js';
7
+ /**
8
+ * Whether this aggregation keeps count order, ignoring the native unknown-bucket suffix: a
9
+ * true/false field, which never has more than two groups and whose `cursor=*` OpenAlex answers
10
+ * with HTTP 400 ("Cannot use cursor pagination when grouping fields that return true or false.").
11
+ *
12
+ * A field is boolean when any dotted segment starts with `is_` or `has_` — the segment, not the
13
+ * leaf, so `has_content.pdf` counts — or when it is one of the unprefixed fields above. That
14
+ * covered every groupable boolean on every entity type in a live sweep (2026-10-28), and no
15
+ * groupable field it matches is anything but true/false. The service (whether to send `cursor`)
16
+ * and `openalex_analyze_trends` (the echoed order and the key-order notice) both read this one
17
+ * predicate, so they cannot disagree about a field.
18
+ */
8
19
  export declare function isBooleanGroupBy(groupBy: string): boolean;
9
20
  /** One upstream filter clause: the canonical key and the value it constrains. */
10
21
  type FilterClause = [key: string, value: string];
@@ -128,7 +139,7 @@ export interface IdentifierResolution extends AutocompleteResult {
128
139
  * works. Any other type leaves the identifier's own types in place — except `keywords`, under
129
140
  * which a bare native-ID shape is a name: keyword IDs are slugs, and `P53` is a keyword.
130
141
  */
131
- export declare function inferIdentifier(query: string, requested?: EntityType): ResolvedIdentifier | undefined;
142
+ export declare function inferIdentifier(query: string, requested?: AutocompleteEntityType): ResolvedIdentifier | undefined;
132
143
  /**
133
144
  * Shape a singleton entity record like an autocomplete match, so both resolution paths of
134
145
  * `openalex_resolve_name` return one result type. `entity_type` is singularized to match what
@@ -152,10 +163,10 @@ export declare function selectRecordKey(entityType: SearchParams['entityType'],
152
163
  * Detect a value the `id:` filter would carry — OpenAlex IDs, bare or in any URL spelling
153
164
  * `parseOpenAlexUrl()` reads, optionally pipe-joined for OR semantics. When matched, `id` is
154
165
  * rewritten to `openalex` (the canonical filter key) and the value is forwarded as written:
155
- * upstream accepts every one of those spellings, and the space around a `|`. Stricter than any
156
- * value so a future generic upstream `id:` filter wouldn't be shadowed — only values that read
157
- * as OpenAlex IDs get redirected. Exported for the timing suite, which runs it on caller-sized
158
- * values.
166
+ * upstream accepts every one of those spellings, and the space around a `|`. Awards are the
167
+ * exception (see `awardIdFilterValue`). Stricter than any value so a future generic upstream
168
+ * `id:` filter wouldn't be shadowed — only values that read as OpenAlex IDs get redirected.
169
+ * Exported for the timing suite, which runs it on caller-sized values.
159
170
  */
160
171
  export declare function isOpenAlexFilterValue(value: string): boolean;
161
172
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"openalex-service.d.ts","sourceRoot":"","sources":["../../../src/services/openalex/openalex-service.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"openalex-service.d.ts","sourceRoot":"","sources":["../../../src/services/openalex/openalex-service.ts"],"names":[],"mappings":"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"}
@@ -10,29 +10,33 @@ import fieldCatalog from './field-catalog.json' with { type: 'json' };
10
10
  import { rankFields } from './field-ranker.js';
11
11
  import { quoteCallerText, quotedCommaValue, upstreamFilterValue } from './filter-values.js';
12
12
  import { normalizeProviderText, normalizeProviderValue } from './provider-text.js';
13
- import { DEFAULT_SELECT, ENTITY_TYPES, } from './types.js';
13
+ import { AUTOCOMPLETE_ENTITY_TYPES, DEFAULT_SELECT, ENTITY_TYPES, } from './types.js';
14
14
  import { redactUrlsInMessage } from './url-redaction.js';
15
15
  /**
16
- * OpenAlex group_by fields that produce only true/false groups and reject
17
- * cursor pagination (HTTP 400). These fields never have more than two groups,
18
- * so cursor-based paging is unnecessary and the upstream rejects `cursor=*`.
19
- */
20
- const BOOLEAN_GROUP_BY_FIELDS = new Set([
21
- 'is_retracted',
22
- 'is_paratext',
23
- 'is_oa',
24
- 'has_orcid',
25
- 'has_doi',
26
- 'has_raw_affiliation_string',
27
- 'open_access.is_oa',
28
- 'apc_paid',
29
- 'has_fulltext',
16
+ * True/false group_by fields whose names carry no `is_`/`has_` segment. OpenAlex names every
17
+ * other boolean field that way, on every entity type.
18
+ */
19
+ const UNPREFIXED_BOOLEAN_GROUP_BY_FIELDS = new Set([
20
+ 'mag_only',
21
+ 'open_access.any_repository_has_fulltext',
30
22
  ]);
31
- /** Whether this aggregation keeps count order, ignoring the native unknown-bucket suffix. */
23
+ /**
24
+ * Whether this aggregation keeps count order, ignoring the native unknown-bucket suffix: a
25
+ * true/false field, which never has more than two groups and whose `cursor=*` OpenAlex answers
26
+ * with HTTP 400 ("Cannot use cursor pagination when grouping fields that return true or false.").
27
+ *
28
+ * A field is boolean when any dotted segment starts with `is_` or `has_` — the segment, not the
29
+ * leaf, so `has_content.pdf` counts — or when it is one of the unprefixed fields above. That
30
+ * covered every groupable boolean on every entity type in a live sweep (2026-10-28), and no
31
+ * groupable field it matches is anything but true/false. The service (whether to send `cursor`)
32
+ * and `openalex_analyze_trends` (the echoed order and the key-order notice) both read this one
33
+ * predicate, so they cannot disagree about a field.
34
+ */
32
35
  export function isBooleanGroupBy(groupBy) {
33
36
  const suffix = ':include_unknown';
34
37
  const baseField = groupBy.endsWith(suffix) ? groupBy.slice(0, -suffix.length) : groupBy;
35
- return BOOLEAN_GROUP_BY_FIELDS.has(baseField);
38
+ return (UNPREFIXED_BOOLEAN_GROUP_BY_FIELDS.has(baseField) ||
39
+ baseField.split('.').some((segment) => segment.startsWith('is_') || segment.startsWith('has_')));
36
40
  }
37
41
  /**
38
42
  * Build an OpenAlex filter string from ordered key-value clauses.
@@ -277,6 +281,7 @@ export function normalizeId(id) {
277
281
  const ENTITY_TYPE_BY_ID_LETTER = {
278
282
  A: 'authors',
279
283
  F: 'funders',
284
+ G: 'awards',
280
285
  I: 'institutions',
281
286
  P: 'publishers',
282
287
  S: 'sources',
@@ -284,21 +289,21 @@ const ENTITY_TYPE_BY_ID_LETTER = {
284
289
  W: 'works',
285
290
  };
286
291
  /**
287
- * Native OpenAlex IDs this server can route. Three letters `OPENALEX_ID_VALUE_PATTERN` accepts
288
- * as *filter values* are deliberately absent here. `C` (the deprecated Concepts entity) and `G`
289
- * are not in `ENTITY_TYPES`, so there is no endpoint to fetch one from. `K` is in `ENTITY_TYPES`,
290
- * but OpenAlex identifies keywords by slug and emits no numeric K-prefixed IDs, so routing that
291
- * shape addressed nothing — a keyword URL is the keyword identifier. All three fall through to
292
- * autocomplete rather than being routed to a path that would 404.
292
+ * Native OpenAlex IDs this server can route. Two letters `OPENALEX_ID_VALUE_PATTERN` accepts as
293
+ * *filter values* are deliberately absent here. `C` (the deprecated Concepts entity) is not in
294
+ * `ENTITY_TYPES`, so there is no endpoint to fetch one from. `K` is in `ENTITY_TYPES`, but
295
+ * OpenAlex identifies keywords by slug and emits no numeric K-prefixed IDs, so routing that shape
296
+ * addressed nothing — a keyword URL is the keyword identifier. Both fall through to autocomplete
297
+ * rather than being routed to a path that would 404.
293
298
  */
294
- const ROUTABLE_OPENALEX_ID = /^([AFIPSTW])\d+$/;
299
+ const ROUTABLE_OPENALEX_ID = /^([AFGIPSTW])\d+$/;
295
300
  /**
296
301
  * A native ID whose letter names the entity type that holds it — every routable letter plus `C`,
297
302
  * a concept ID, which names a type this server does not serve. Read case-insensitively: OpenAlex
298
303
  * answers a lower-case ID as it does the upper-case one. `V` (a legacy venue ID that still
299
- * resolves under `/sources/`), `K`, and `G` are absent, so `search()` checks none of them.
304
+ * resolves under `/sources/`) and `K` are absent, so `search()` checks neither.
300
305
  */
301
- const LETTERED_OPENALEX_ID = /^([ACFIPSTW])\d+$/i;
306
+ const LETTERED_OPENALEX_ID = /^([ACFGIPSTW])\d+$/i;
302
307
  /**
303
308
  * Refuse a native ID whose letter names a different entity type than the lookup's. No record of
304
309
  * one type carries another type's letter and every cross-letter lookup 404s upstream, so the
@@ -703,21 +708,39 @@ function resolveSelect(entityType, select) {
703
708
  * to detect OR-joined ID lists.
704
709
  */
705
710
  const OPENALEX_ID_VALUE_PATTERN = /^[WAISTKPFCG]\d+$/i;
711
+ /** One `id` filter segment as the bare ID it names, or the segment itself when it is no URL. */
712
+ function filterSegmentId(segment) {
713
+ const trimmed = segment.trim();
714
+ const fromUrl = parseOpenAlexUrl(trimmed);
715
+ return fromUrl?.kind === 'native' ? fromUrl.id : trimmed;
716
+ }
706
717
  /**
707
718
  * Detect a value the `id:` filter would carry — OpenAlex IDs, bare or in any URL spelling
708
719
  * `parseOpenAlexUrl()` reads, optionally pipe-joined for OR semantics. When matched, `id` is
709
720
  * rewritten to `openalex` (the canonical filter key) and the value is forwarded as written:
710
- * upstream accepts every one of those spellings, and the space around a `|`. Stricter than any
711
- * value so a future generic upstream `id:` filter wouldn't be shadowed — only values that read
712
- * as OpenAlex IDs get redirected. Exported for the timing suite, which runs it on caller-sized
713
- * values.
721
+ * upstream accepts every one of those spellings, and the space around a `|`. Awards are the
722
+ * exception (see `awardIdFilterValue`). Stricter than any value so a future generic upstream
723
+ * `id:` filter wouldn't be shadowed — only values that read as OpenAlex IDs get redirected.
724
+ * Exported for the timing suite, which runs it on caller-sized values.
714
725
  */
715
726
  export function isOpenAlexFilterValue(value) {
716
- return value.split('|').every((segment) => {
717
- const trimmed = segment.trim();
718
- const fromUrl = parseOpenAlexUrl(trimmed);
719
- return OPENALEX_ID_VALUE_PATTERN.test(fromUrl?.kind === 'native' ? fromUrl.id : trimmed);
720
- });
727
+ return value
728
+ .split('|')
729
+ .every((segment) => OPENALEX_ID_VALUE_PATTERN.test(filterSegmentId(segment)));
730
+ }
731
+ /**
732
+ * An `id` filter value for awards, which have no `openalex` filter (a 400) and whose `id` filter
733
+ * matches only the `https://openalex.org/G…` URL — a bare `G…`, in either case, matches nothing
734
+ * with no error. Each OR segment is rewritten to that URL, its letter upper-cased.
735
+ */
736
+ function awardIdFilterValue(value) {
737
+ return value
738
+ .split('|')
739
+ .map((segment) => {
740
+ const id = filterSegmentId(segment);
741
+ return `https://openalex.org/${id.charAt(0).toUpperCase()}${id.slice(1)}`;
742
+ })
743
+ .join('|');
721
744
  }
722
745
  /**
723
746
  * Rewrite one caller-supplied filter key to the upstream OpenAlex name, before the
@@ -733,8 +756,9 @@ export function translateFilterKey(entityType, key) {
733
756
  }
734
757
  /**
735
758
  * Rewrite caller-supplied filter keys to the upstream OpenAlex names. Works-only key map plus
736
- * a universal `id` → `openalex` rewrite when the value looks like an OpenAlex ID. Values pass
737
- * through unchanged.
759
+ * an `id` → `openalex` rewrite when the value looks like an OpenAlex ID, on every type but
760
+ * awards, whose ID values stay under `id` in the URL form it matches (`awardIdFilterValue`).
761
+ * Every other value passes through unchanged.
738
762
  *
739
763
  * Emits clauses rather than a record because an alias and its canonical name are two separate
740
764
  * constraints that resolve to one key — `{year: "2020", publication_year: "2024"}` is a request
@@ -745,9 +769,11 @@ export function translateFilterKey(entityType, key) {
745
769
  */
746
770
  function translateFilters(entityType, filters) {
747
771
  const out = [];
748
- for (const [rawKey, value] of Object.entries(filters)) {
772
+ for (const [rawKey, rawValue] of Object.entries(filters)) {
749
773
  const aliased = translateFilterKey(entityType, rawKey);
750
- const finalKey = aliased === 'id' && isOpenAlexFilterValue(value) ? 'openalex' : aliased;
774
+ const openAlexIds = aliased === 'id' && isOpenAlexFilterValue(rawValue);
775
+ const finalKey = openAlexIds && entityType !== 'awards' ? 'openalex' : aliased;
776
+ const value = openAlexIds && entityType === 'awards' ? awardIdFilterValue(rawValue) : rawValue;
751
777
  if (out.some(([key, existing]) => key === finalKey && existing === value))
752
778
  continue;
753
779
  out.push([finalKey, value]);
@@ -777,7 +803,7 @@ const REQUEST_TIMEOUT_MS = 10_000;
777
803
  * the name search reports a timeout without being sent.
778
804
  */
779
805
  const NAME_FALLBACK_DEADLINE_MS = 45_000;
780
- const KNOWN_AUTOCOMPLETE_TYPES = new Set(ENTITY_TYPES.map((type) => type.replace(/s$/, '')));
806
+ const KNOWN_AUTOCOMPLETE_TYPES = new Set(AUTOCOMPLETE_ENTITY_TYPES.map((type) => type.replace(/s$/, '')));
781
807
  /**
782
808
  * `JsonRpcErrorCode` → `{ factory, reason }` for shaping normalized OpenAlex throws.
783
809
  * Driven by `httpStatusToErrorCode` so the status-table stays in lockstep with the
@@ -1437,40 +1463,35 @@ class OpenAlexService {
1437
1463
  if (select) {
1438
1464
  queryParams.select = select.join(',');
1439
1465
  }
1440
- // Sampling overrides per_page — upstream truncates results at per_page even when
1441
- // sample is larger, which silently drops samples the caller asked for. Aligning the
1442
- // two means callers get exactly `sample` results in a single page.
1466
+ // `per_page` sizes every page, a sample's included: OpenAlex draws `sample` records and
1467
+ // serves them `per_page` at a time, so a draw larger than one page is walked with `page`.
1468
+ queryParams.per_page = String(params.perPage ?? 25);
1443
1469
  if (params.sample !== undefined) {
1444
1470
  queryParams.sample = String(params.sample);
1445
- queryParams.per_page = String(params.sample);
1446
1471
  if (params.seed !== undefined)
1447
1472
  queryParams.seed = params.seed;
1448
1473
  }
1449
- else {
1450
- queryParams.per_page = String(params.perPage ?? 25);
1451
- }
1452
- // Semantic search doesn't support cursor pagination — OpenAlex answers `search.semantic`
1453
- // plus a cursor with a 400 — so it walks its candidate set with `page`/`per_page` instead.
1454
- // Sampling returns one page only, so cursor is mutually exclusive there too. Both
1455
- // combinations are rejected at the tool layer; the service never sends them together.
1456
- // The tool also rejects `sample` under semantic mode (OpenAlex ignores it there, and the
1457
- // population request below would pair `search.semantic` with `cursor=*`) and `sample`
1458
- // beside `sort` (OpenAlex refuses the pair), so neither reaches this method.
1459
- if (params.searchMode === 'semantic') {
1474
+ // Semantic search and samples walk with `page` — OpenAlex answers `search.semantic` plus a
1475
+ // cursor with a 400, and pages a seeded draw by number. Every other listing walks by cursor.
1476
+ // The tool rejects `cursor` on either, `sample` under semantic mode (OpenAlex ignores it
1477
+ // there, and the population request below would pair `search.semantic` with `cursor=*`),
1478
+ // `sample` beside `sort` (OpenAlex refuses the pair), and `page` on an unseeded sample or
1479
+ // past the first 10,000 records of one (both 400s upstream), so none reaches this method.
1480
+ if (params.searchMode === 'semantic' || params.sample !== undefined) {
1460
1481
  if (params.page !== undefined)
1461
1482
  queryParams.page = String(params.page);
1462
1483
  }
1463
- else if (params.sample === undefined) {
1484
+ else {
1464
1485
  queryParams.cursor = params.cursor ?? '*';
1465
1486
  }
1466
1487
  const searchRequest = this.request(`/${params.entityType}`, queryParams, ctx);
1467
- // Sampling: OpenAlex returns the sample size in `meta.count`, not the population
1468
- // matching the filters. The output schema documents `count` as "Total results
1469
- // matching the query/filters" — issue a parallel per_page=1 lookup without
1470
- // `sample` to recover the true population and overwrite `count` with it.
1488
+ // Sampling: OpenAlex returns the drawn size, min(sample, population), in `meta.count`, not
1489
+ // the population matching the filters. The output schema documents `count` as "Total
1490
+ // results matching the query/filters" — issue a parallel per_page=1 lookup without the
1491
+ // sample's own parameters to recover the true population and overwrite `count` with it.
1471
1492
  let populationRequest;
1472
1493
  if (params.sample !== undefined) {
1473
- const { sample: _sample, seed: _seed, ...filterParams } = queryParams;
1494
+ const { sample: _sample, seed: _seed, page: _page, ...filterParams } = queryParams;
1474
1495
  populationRequest = this.request(`/${params.entityType}`, { ...filterParams, per_page: '1', cursor: '*' }, ctx);
1475
1496
  }
1476
1497
  const [data, populationData] = await Promise.all([searchRequest, populationRequest]);
@@ -1518,8 +1539,8 @@ class OpenAlexService {
1518
1539
  if (params.perPage !== undefined) {
1519
1540
  queryParams.per_page = String(params.perPage);
1520
1541
  }
1521
- // Boolean group_by fields (is_retracted, has_orcid, etc.) only produce
1522
- // two groups (true/false) and reject cursor pagination with a 400.
1542
+ // Boolean group_by fields (see `isBooleanGroupBy`) only produce two groups (true/false) and
1543
+ // reject cursor pagination with a 400, so they keep count order even under order "key".
1523
1544
  //
1524
1545
  // For all other fields, cursor behaviour follows the sort order:
1525
1546
  // - count-desc (default): omit cursor on the first page so OpenAlex returns groups