@cyanheads/openalex-mcp-server 0.10.1 → 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.
- package/AGENTS.md +14 -11
- package/CLAUDE.md +14 -11
- package/README.md +34 -16
- package/changelog/0.11.x/0.11.0.md +41 -0
- package/dist/config/server-config.d.ts.map +1 -1
- package/dist/config/server-config.js +1 -1
- package/dist/config/server-config.js.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/mcp-server/prompts/definitions/literature-review.prompt.js +1 -1
- package/dist/mcp-server/prompts/definitions/research-landscape.prompt.js +1 -1
- package/dist/mcp-server/tools/definitions/analyze-trends.tool.d.ts +1 -0
- package/dist/mcp-server/tools/definitions/analyze-trends.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/analyze-trends.tool.js +10 -5
- package/dist/mcp-server/tools/definitions/analyze-trends.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/citation-graph.tool.d.ts +6 -9
- package/dist/mcp-server/tools/definitions/citation-graph.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/citation-graph.tool.js +43 -33
- package/dist/mcp-server/tools/definitions/citation-graph.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/describe-fields.tool.d.ts +1 -0
- package/dist/mcp-server/tools/definitions/describe-fields.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/describe-fields.tool.js +25 -1
- package/dist/mcp-server/tools/definitions/describe-fields.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/resolve-name.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/resolve-name.tool.js +44 -16
- package/dist/mcp-server/tools/definitions/resolve-name.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/search-entities.tool.d.ts +22 -18
- package/dist/mcp-server/tools/definitions/search-entities.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/search-entities.tool.js +194 -85
- package/dist/mcp-server/tools/definitions/search-entities.tool.js.map +1 -1
- package/dist/mcp-server/tools/escape-markdown.d.ts +26 -11
- package/dist/mcp-server/tools/escape-markdown.d.ts.map +1 -1
- package/dist/mcp-server/tools/escape-markdown.js +95 -26
- package/dist/mcp-server/tools/escape-markdown.js.map +1 -1
- package/dist/mcp-server/tools/page-size.d.ts +38 -7
- package/dist/mcp-server/tools/page-size.d.ts.map +1 -1
- package/dist/mcp-server/tools/page-size.js +37 -11
- package/dist/mcp-server/tools/page-size.js.map +1 -1
- package/dist/mcp-server/tools/record-page.d.ts +174 -0
- package/dist/mcp-server/tools/record-page.d.ts.map +1 -0
- package/dist/mcp-server/tools/record-page.js +392 -0
- package/dist/mcp-server/tools/record-page.js.map +1 -0
- package/dist/mcp-server/tools/render-entity-record.d.ts +3 -1
- package/dist/mcp-server/tools/render-entity-record.d.ts.map +1 -1
- package/dist/mcp-server/tools/render-entity-record.js +7 -2
- package/dist/mcp-server/tools/render-entity-record.js.map +1 -1
- package/dist/services/openalex/field-catalog.json +107 -0
- package/dist/services/openalex/openalex-service.d.ts +18 -7
- package/dist/services/openalex/openalex-service.d.ts.map +1 -1
- package/dist/services/openalex/openalex-service.js +84 -63
- package/dist/services/openalex/openalex-service.js.map +1 -1
- package/dist/services/openalex/types.d.ts +13 -2
- package/dist/services/openalex/types.d.ts.map +1 -1
- package/dist/services/openalex/types.js +28 -1
- package/dist/services/openalex/types.js.map +1 -1
- package/package.json +1 -1
- package/server.json +5 -5
- package/skills/openalex-guide/SKILL.md +77 -0
- package/dist/mcp-server/tools/response-budget.d.ts +0 -160
- package/dist/mcp-server/tools/response-budget.d.ts.map +0 -1
- package/dist/mcp-server/tools/response-budget.js +0 -536
- 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":"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
|
|
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
|
-
/**
|
|
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?:
|
|
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 `|`.
|
|
156
|
-
*
|
|
157
|
-
*
|
|
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":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,wBAAwB,CAAC;AA6BtD,OAAO,EACL,KAAK,aAAa,EAClB,KAAK,aAAa,
|
|
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
|
-
*
|
|
17
|
-
*
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
'
|
|
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
|
-
/**
|
|
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
|
|
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.
|
|
288
|
-
*
|
|
289
|
-
*
|
|
290
|
-
*
|
|
291
|
-
*
|
|
292
|
-
*
|
|
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 = /^([
|
|
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/`)
|
|
304
|
+
* resolves under `/sources/`) and `K` are absent, so `search()` checks neither.
|
|
300
305
|
*/
|
|
301
|
-
const LETTERED_OPENALEX_ID = /^([
|
|
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 `|`.
|
|
711
|
-
*
|
|
712
|
-
*
|
|
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
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
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
|
-
*
|
|
737
|
-
*
|
|
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,
|
|
772
|
+
for (const [rawKey, rawValue] of Object.entries(filters)) {
|
|
749
773
|
const aliased = translateFilterKey(entityType, rawKey);
|
|
750
|
-
const
|
|
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(
|
|
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
|
-
//
|
|
1441
|
-
//
|
|
1442
|
-
|
|
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
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
//
|
|
1453
|
-
//
|
|
1454
|
-
//
|
|
1455
|
-
|
|
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
|
|
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
|
|
1468
|
-
// matching the filters. The output schema documents `count` as "Total
|
|
1469
|
-
// matching the query/filters" — issue a parallel per_page=1 lookup without
|
|
1470
|
-
//
|
|
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 (
|
|
1522
|
-
//
|
|
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
|