@cyanheads/openalex-mcp-server 0.8.0 → 0.10.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 +25 -7
- package/CLAUDE.md +25 -7
- package/Dockerfile +17 -8
- package/README.md +28 -19
- package/changelog/0.10.x/0.10.0.md +30 -0
- package/changelog/0.9.x/0.9.0.md +48 -0
- package/dist/config/server-config.d.ts +2 -0
- package/dist/config/server-config.d.ts.map +1 -1
- package/dist/config/server-config.js +10 -0
- package/dist/config/server-config.js.map +1 -1
- package/dist/index.js +1 -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 +35 -4
- package/dist/mcp-server/tools/definitions/analyze-trends.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/analyze-trends.tool.js +86 -41
- package/dist/mcp-server/tools/definitions/analyze-trends.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/citation-graph.tool.d.ts +48 -5
- package/dist/mcp-server/tools/definitions/citation-graph.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/citation-graph.tool.js +77 -24
- package/dist/mcp-server/tools/definitions/citation-graph.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/describe-fields.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/describe-fields.tool.js +34 -4
- package/dist/mcp-server/tools/definitions/describe-fields.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/resolve-name.tool.d.ts +43 -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 +182 -40
- package/dist/mcp-server/tools/definitions/resolve-name.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/search-entities.tool.d.ts +64 -6
- package/dist/mcp-server/tools/definitions/search-entities.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/search-entities.tool.js +138 -35
- package/dist/mcp-server/tools/definitions/search-entities.tool.js.map +1 -1
- package/dist/mcp-server/tools/page-size.d.ts +18 -0
- package/dist/mcp-server/tools/page-size.d.ts.map +1 -0
- package/dist/mcp-server/tools/page-size.js +28 -0
- package/dist/mcp-server/tools/page-size.js.map +1 -0
- package/dist/mcp-server/tools/render-budget.d.ts +4 -2
- package/dist/mcp-server/tools/render-budget.d.ts.map +1 -1
- package/dist/mcp-server/tools/render-budget.js +4 -2
- package/dist/mcp-server/tools/render-budget.js.map +1 -1
- package/dist/mcp-server/tools/response-budget.d.ts +3 -3
- package/dist/mcp-server/tools/response-budget.d.ts.map +1 -1
- package/dist/mcp-server/tools/response-budget.js +64 -35
- package/dist/mcp-server/tools/response-budget.js.map +1 -1
- package/dist/services/openalex/budget.d.ts +3 -1
- package/dist/services/openalex/budget.d.ts.map +1 -1
- package/dist/services/openalex/budget.js +3 -1
- package/dist/services/openalex/budget.js.map +1 -1
- package/dist/services/openalex/field-catalog.json +50 -1
- package/dist/services/openalex/filter-values.d.ts +48 -0
- package/dist/services/openalex/filter-values.d.ts.map +1 -0
- package/dist/services/openalex/filter-values.js +350 -0
- package/dist/services/openalex/filter-values.js.map +1 -0
- package/dist/services/openalex/iso-codes.json +1565 -0
- package/dist/services/openalex/openalex-service.d.ts +176 -20
- package/dist/services/openalex/openalex-service.d.ts.map +1 -1
- package/dist/services/openalex/openalex-service.js +573 -127
- package/dist/services/openalex/openalex-service.js.map +1 -1
- package/dist/services/openalex/provider-text.d.ts +7 -6
- package/dist/services/openalex/provider-text.d.ts.map +1 -1
- package/dist/services/openalex/provider-text.js +485 -61
- package/dist/services/openalex/provider-text.js.map +1 -1
- package/dist/services/openalex/types.d.ts +5 -2
- package/dist/services/openalex/types.d.ts.map +1 -1
- package/package.json +14 -12
- package/server.json +35 -21
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"filter-values.d.ts","sourceRoot":"","sources":["../../../src/services/openalex/filter-values.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAuBH;;;GAGG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEpD;AAED,qFAAqF;AACrF,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAkDxE;AAoID;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAY/E;AA4ED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAuCtE"}
|
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview A filter value in the form OpenAlex reads: country and `language` values
|
|
3
|
+
* converted to the codes OpenAlex stores; a value with spaces or a comma on a key OpenAlex matches
|
|
4
|
+
* as whole text — a name, city, region, raw affiliation string, award number, or `biblio` field —
|
|
5
|
+
* double-quoted, so its spaces and commas stay part of it; and the AND operator — a `+`, or on a
|
|
6
|
+
* key where words mean AND, a space between them — sent as the AND it spells. Values that are
|
|
7
|
+
* text rather than filter syntax keep their `+` literal: free-text search phrases, DOIs, dates,
|
|
8
|
+
* URLs, exactly matched names, `biblio` fields, award numbers, and any value the caller wrapped
|
|
9
|
+
* in quotes. A value that puts an AND operator beside a `|` (OR) fails, since OpenAlex splits on
|
|
10
|
+
* `|` first and drops every alternative that ANDs more than one value. A value sent double-quoted
|
|
11
|
+
* holds no `"` of its own, which would end the quoted phrase early and let the text after it
|
|
12
|
+
* parse as further filter clauses.
|
|
13
|
+
* @module services/openalex/filter-values
|
|
14
|
+
*/
|
|
15
|
+
import { validationError } from '@cyanheads/mcp-ts-core/errors';
|
|
16
|
+
import isoCodes from './iso-codes.json' with { type: 'json' };
|
|
17
|
+
/**
|
|
18
|
+
* Lookups from the bundled ISO 3166-1 / ISO 639-2 table (`scripts/generate-iso-codes.ts`).
|
|
19
|
+
* Maps rather than the JSON objects, so a segment such as `constructor` or `__proto__` reads as
|
|
20
|
+
* no entry instead of an inherited member.
|
|
21
|
+
*/
|
|
22
|
+
const COUNTRY_BY_ALPHA3 = new Map(Object.entries(isoCodes.countries.alpha3));
|
|
23
|
+
const COUNTRY_BY_NUMERIC = new Map(Object.entries(isoCodes.countries.numeric));
|
|
24
|
+
const COUNTRY_BY_NAME = new Map(Object.entries(isoCodes.countries.names));
|
|
25
|
+
const LANGUAGE_BY_ALPHA3 = new Map(Object.entries(isoCodes.languages.alpha3));
|
|
26
|
+
const LANGUAGE_BY_NAME = new Map(Object.entries(isoCodes.languages.names));
|
|
27
|
+
const TWO_LETTERS = /^[A-Za-z]{2}$/;
|
|
28
|
+
const THREE_LETTERS = /^[A-Za-z]{3}$/;
|
|
29
|
+
/** The most of a caller's text an error message quotes; the error's `data` keeps it whole. */
|
|
30
|
+
const ECHO_MAX_CHARS = 200;
|
|
31
|
+
/**
|
|
32
|
+
* Caller text as an error message quotes it: JSON-encoded, so a line break stays escaped, and
|
|
33
|
+
* capped, so caller-sized text cannot bloat the message.
|
|
34
|
+
*/
|
|
35
|
+
export function quoteCallerText(text) {
|
|
36
|
+
return JSON.stringify(text.length > ECHO_MAX_CHARS ? `${text.slice(0, ECHO_MAX_CHARS)}…` : text);
|
|
37
|
+
}
|
|
38
|
+
/** Parse native clauses without splitting quoted commas or colons inside a value. */
|
|
39
|
+
export function parseNativeFilters(input) {
|
|
40
|
+
const filters = Object.create(null);
|
|
41
|
+
let quoted = false;
|
|
42
|
+
let start = 0;
|
|
43
|
+
let colon = -1;
|
|
44
|
+
for (let index = 0; index <= input.length; index++) {
|
|
45
|
+
const char = input[index];
|
|
46
|
+
if (char === '"')
|
|
47
|
+
quoted = !quoted;
|
|
48
|
+
if (char === ':' && colon === -1 && !quoted)
|
|
49
|
+
colon = index;
|
|
50
|
+
if (index !== input.length && (char !== ',' || quoted))
|
|
51
|
+
continue;
|
|
52
|
+
if (quoted) {
|
|
53
|
+
throw validationError('Native filter string has unmatched double quotes. Pair the quotes, or pass filters as a record object with one key per field.', {
|
|
54
|
+
reason: 'invalid_filter_string',
|
|
55
|
+
recovery: {
|
|
56
|
+
hint: 'Pass filters as a record object with one key per field, and pair any double quotes in search phrases.',
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
const key = input.slice(start, colon === -1 ? start : colon).trim();
|
|
61
|
+
if (colon < start || key.length === 0) {
|
|
62
|
+
throw validationError('Each native filter clause needs a nonempty key followed by a colon. Pass filters as a record object with one key per field.', {
|
|
63
|
+
reason: 'invalid_filter_string',
|
|
64
|
+
recovery: {
|
|
65
|
+
hint: 'Pass filters as a record object with one key per field, and pair any double quotes in search phrases.',
|
|
66
|
+
},
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
if (Object.hasOwn(filters, key)) {
|
|
70
|
+
throw validationError('Native filter string repeats a key. A filters record object holds one key per field; combine alternatives with "|", or run separate calls.', {
|
|
71
|
+
reason: 'duplicate_filter_string_key',
|
|
72
|
+
filterKey: key,
|
|
73
|
+
recovery: {
|
|
74
|
+
hint: 'A filters record object holds one key per field; combine alternatives with "|", or run separate calls.',
|
|
75
|
+
},
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
filters[key] = input.slice(colon + 1, index);
|
|
79
|
+
start = index + 1;
|
|
80
|
+
colon = -1;
|
|
81
|
+
}
|
|
82
|
+
return filters;
|
|
83
|
+
}
|
|
84
|
+
/** Reject an unpaired quote only on values that remain unquoted on the upstream wire. */
|
|
85
|
+
function assertPairedQuotes(key, value) {
|
|
86
|
+
let quoted = false;
|
|
87
|
+
for (let index = value.indexOf('"'); index !== -1; index = value.indexOf('"', index + 1))
|
|
88
|
+
quoted = !quoted;
|
|
89
|
+
if (quoted) {
|
|
90
|
+
throw validationError(`Filter ${quoteCallerText(key)} value has an unmatched double quote. Pair double quotes used for a search phrase, or remove the stray quote.`, { reason: 'unmatched_quote_in_filter_value', filterKey: key });
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Reject a double-quoted value, `sent` exactly as the clause sends it, when a `"` sits inside its
|
|
95
|
+
* outer pair (a leading `!` aside). OpenAlex ends a quoted phrase at the next `"` with no escape
|
|
96
|
+
* for one inside, so an interior quote closes the phrase early and the text after it reads as
|
|
97
|
+
* further filter clauses. Checking the value as sent, after any wrapping, is what catches a
|
|
98
|
+
* caller's quote pair the wrap encloses (`! "New York"` goes out as `"! "New York""`). Runs before
|
|
99
|
+
* any request.
|
|
100
|
+
*/
|
|
101
|
+
function assertNoInteriorQuote(key, sent) {
|
|
102
|
+
const trimmed = sent.trim();
|
|
103
|
+
const body = trimmed.startsWith('!') ? trimmed.slice(1) : trimmed;
|
|
104
|
+
const inner = body.length >= 2 && body.startsWith('"') && body.endsWith('"') ? body.slice(1, -1) : body;
|
|
105
|
+
if (inner.includes('"')) {
|
|
106
|
+
throw validationError(`Filter ${quoteCallerText(key)} value is sent to OpenAlex as a quoted phrase and cannot contain a double quote of its own. Remove the double quote from the value.`, { reason: 'quote_in_filter_value', filterKey: key });
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* The key suffixes OpenAlex country-code fields end in, across every entity type — and nothing
|
|
111
|
+
* else: `countries_distinct_count` is a number.
|
|
112
|
+
*/
|
|
113
|
+
const COUNTRY_KEY_SUFFIXES = ['country_code', 'country_codes', 'countries'];
|
|
114
|
+
/** A `.search` segment, at the end of the key or followed by a suffix such as `.exact`. */
|
|
115
|
+
const FREE_TEXT_SEARCH_KEY = /\.search(?:\.|$)/;
|
|
116
|
+
/**
|
|
117
|
+
* Whether `key` is a free-text search filter — `title.search`, `abstract.search.exact`,
|
|
118
|
+
* `title_abstract_keywords.search`, `semantic.search` — whose value is a phrase rather than
|
|
119
|
+
* filter syntax. `quotedCommaValue()` sends such a value holding a comma double-quoted, so the
|
|
120
|
+
* comma stays part of the phrase, and `upstreamFilterValue()` keeps its `+` literal.
|
|
121
|
+
*/
|
|
122
|
+
function isFreeTextSearchKey(key) {
|
|
123
|
+
return FREE_TEXT_SEARCH_KEY.test(key);
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Whether a `+` in this (hyphen-normalized) key's value is a literal character, because the value
|
|
127
|
+
* is text rather than filter syntax: a free-text search filter; a DOI filter (`doi`,
|
|
128
|
+
* `doi_starts_with`, `awards.doi`, `ids.doi`), whose values include SICI DOIs such as
|
|
129
|
+
* `10.1002/1097-0142(19940801)74:3+<1030::…>`; a date (every `_date` key), where the `+hh:mm` of
|
|
130
|
+
* a timestamp — which `from_created_date` and `to_updated_date` take, unlike `publication_date` —
|
|
131
|
+
* is its UTC offset; a URL
|
|
132
|
+
* (`primary_location.landing_page_url`), which can carry a `+` of its own — every `_url` key but a
|
|
133
|
+
* `has_` boolean such as `has_pdf_url`; a name matched exactly (`display_name`,
|
|
134
|
+
* `awards.funder_display_name`), such as the keyword `C++` or the funder `Erasmus+`; a `biblio`
|
|
135
|
+
* field, such as the combined issue `1+2`; and an award number as its funder deposited it
|
|
136
|
+
* (`awards.funder_award_id`).
|
|
137
|
+
*/
|
|
138
|
+
function keepsPlusLiteral(field) {
|
|
139
|
+
if (isFreeTextSearchKey(field) || field.startsWith('biblio.'))
|
|
140
|
+
return true;
|
|
141
|
+
const leaf = field.slice(field.lastIndexOf('.') + 1);
|
|
142
|
+
return (leaf === 'doi' ||
|
|
143
|
+
leaf === 'doi_starts_with' ||
|
|
144
|
+
leaf === 'display_name' ||
|
|
145
|
+
leaf === 'funder_display_name' ||
|
|
146
|
+
leaf === 'funder_award_id' ||
|
|
147
|
+
leaf.endsWith('_date') ||
|
|
148
|
+
(leaf.endsWith('_url') && !leaf.startsWith('has_')));
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* The leaves of the (hyphen-normalized) keys whose values OpenAlex matches as whole text and
|
|
152
|
+
* stores with spaces: names (`display_name`, `awards.funder_display_name`), cities and regions
|
|
153
|
+
* (`geo.city`, `authorships.institutions.geo.region`), raw affiliation strings, award numbers as
|
|
154
|
+
* their funder deposited them (`DE AC02 05CH11231`), and an author's `block_key` (`j priem`).
|
|
155
|
+
* Every `biblio` field (issue `Supplement 1`) and `parsed_longest_name` part is such a key too.
|
|
156
|
+
*/
|
|
157
|
+
const PHRASE_KEY_LEAVES = new Set([
|
|
158
|
+
'display_name',
|
|
159
|
+
'funder_display_name',
|
|
160
|
+
'funder_award_id',
|
|
161
|
+
'city',
|
|
162
|
+
'region',
|
|
163
|
+
'raw_affiliation_strings',
|
|
164
|
+
'block_key',
|
|
165
|
+
]);
|
|
166
|
+
function isPhraseKey(field) {
|
|
167
|
+
return (field.startsWith('biblio.') ||
|
|
168
|
+
field.startsWith('parsed_longest_name.') ||
|
|
169
|
+
PHRASE_KEY_LEAVES.has(field.slice(field.lastIndexOf('.') + 1)));
|
|
170
|
+
}
|
|
171
|
+
/** Whether `value` is already wrapped in double quotes, a leading `!` aside: `!"New York"`. */
|
|
172
|
+
function isQuoted(value) {
|
|
173
|
+
const body = value.startsWith('!') ? value.slice(1) : value;
|
|
174
|
+
return body.length >= 2 && body.startsWith('"') && body.endsWith('"');
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* The value as one double-quoted phrase, when a phrase key's value has a space between words and
|
|
178
|
+
* the caller has not quoted it; otherwise `undefined`. OpenAlex reads an unquoted space as AND,
|
|
179
|
+
* so `geo.city:New York` asks for a city named both `New` and `York` and matches nothing, while
|
|
180
|
+
* `geo.city:"New York"` matches the city. The quote goes around the whole value: OpenAlex strips
|
|
181
|
+
* it before reading `!` and `|`, so `"!New York"` excludes the city and `"New York|Boston"`
|
|
182
|
+
* matches either — and it reads a quote around a single `|` alternative as part of that
|
|
183
|
+
* alternative. It keeps edge whitespace inside the quotes (`" New York "` matches nothing), so the
|
|
184
|
+
* phrase is sent trimmed. A value with a `+` that means AND is left alone: no single clause ANDs a
|
|
185
|
+
* phrase with another value, quoted or not, so it goes out as it did before.
|
|
186
|
+
*/
|
|
187
|
+
function quotedPhrase(field, value) {
|
|
188
|
+
if (!isPhraseKey(field))
|
|
189
|
+
return;
|
|
190
|
+
const phrase = value.trim();
|
|
191
|
+
if (!phrase.includes(' ') || isQuoted(phrase))
|
|
192
|
+
return;
|
|
193
|
+
if (phrase.includes('+') && !keepsPlusLiteral(field))
|
|
194
|
+
return;
|
|
195
|
+
return `"${phrase}"`;
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* A value holding a comma as its clause sends it, or `undefined` where the key takes no phrase
|
|
199
|
+
* and the comma could only read as OpenAlex's clause separator. A free-text search value or a
|
|
200
|
+
* phrase key's value goes out whole and double-quoted, as written — `|` still ORs inside the
|
|
201
|
+
* quotes, a leading `!` still negates the whole value, and a `+` is the literal character, which
|
|
202
|
+
* no clause could AND with a phrase anyway. A phrase key's value is trimmed first, as
|
|
203
|
+
* `quotedPhrase()` trims, and one the caller quoted (a leading `!` aside) is sent as written.
|
|
204
|
+
* Either way the value as sent cannot hold a `"` inside its outer pair, which would end the quoted
|
|
205
|
+
* phrase early (`quote_in_filter_value`).
|
|
206
|
+
*/
|
|
207
|
+
export function quotedCommaValue(key, value) {
|
|
208
|
+
let sent;
|
|
209
|
+
if (isFreeTextSearchKey(key)) {
|
|
210
|
+
sent = value.startsWith('"') && value.endsWith('"') ? value : `"${value}"`;
|
|
211
|
+
}
|
|
212
|
+
else if (isPhraseKey(key.replaceAll('-', '_'))) {
|
|
213
|
+
const phrase = value.trim();
|
|
214
|
+
sent = isQuoted(phrase) ? phrase : `"${phrase}"`;
|
|
215
|
+
}
|
|
216
|
+
else {
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
assertNoInteriorQuote(key, sent);
|
|
220
|
+
return sent;
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* One country segment as OpenAlex stores it: an alpha-3 code, numeric code, or English name —
|
|
224
|
+
* ISO's, or the one OpenAlex displays for the country (`Russia`, `Ivory Coast`) — becomes its
|
|
225
|
+
* alpha-2 code, and `UK` becomes `GB` (ISO 3166-1 reserves it for the United Kingdom). Two- and
|
|
226
|
+
* three-letter segments it does not map, `null`, empty segments, and the
|
|
227
|
+
* `countries/DE` path forms are sent as given — stored values include non-ISO two-letter codes
|
|
228
|
+
* (`XK`), MARC three-letter codes (`NYU`), and alpha-3 tags reachable only by path. Anything
|
|
229
|
+
* else cannot be a country code and matches nothing, so it fails here — a segment holding a space
|
|
230
|
+
* included, since a space is part of a name (`South Korea`) and never separates two codes
|
|
231
|
+
* (`DE US`), and the message says so.
|
|
232
|
+
*/
|
|
233
|
+
function countrySegment(segment, key) {
|
|
234
|
+
if (segment === '' || segment === 'null' || segment.includes('/'))
|
|
235
|
+
return segment;
|
|
236
|
+
if (TWO_LETTERS.test(segment))
|
|
237
|
+
return segment.toUpperCase() === 'UK' ? 'GB' : segment;
|
|
238
|
+
if (THREE_LETTERS.test(segment))
|
|
239
|
+
return COUNTRY_BY_ALPHA3.get(segment.toUpperCase()) ?? segment;
|
|
240
|
+
const code = COUNTRY_BY_NUMERIC.get(segment) ?? COUNTRY_BY_NAME.get(segment.toLowerCase());
|
|
241
|
+
if (code !== undefined)
|
|
242
|
+
return code;
|
|
243
|
+
const spaced = segment.includes(' ')
|
|
244
|
+
? ' A space is part of a country name ("South Korea"), never a separator: join codes with "|" for OR or "+" for AND.'
|
|
245
|
+
: '';
|
|
246
|
+
throw validationError(`Filter ${quoteCallerText(key)} value ${quoteCallerText(segment)} is not a country code. Country filters take ISO 3166-1 alpha-2 codes such as "DE"; an alpha-3 code ("DEU"), a numeric code ("276"), or an English country name ("Germany") is converted to one, and "null" matches records with no country.${spaced}`, { reason: 'invalid_country_code', filterKey: key, segment });
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* One `language` segment as OpenAlex stores it: an ISO 639-2 code with an ISO 639-1 equivalent
|
|
250
|
+
* becomes that code (`eng` → `en`), and an English language name becomes its code (`English` →
|
|
251
|
+
* `en`). Everything else is sent as given — stored values include three-letter codes with no
|
|
252
|
+
* ISO 639-1 equivalent (`ceb`), regional tags (`zh-cn`), and native names (`中文`) — so no
|
|
253
|
+
* language value is rejected.
|
|
254
|
+
*/
|
|
255
|
+
function languageSegment(segment) {
|
|
256
|
+
if (TWO_LETTERS.test(segment))
|
|
257
|
+
return segment;
|
|
258
|
+
if (THREE_LETTERS.test(segment))
|
|
259
|
+
return LANGUAGE_BY_ALPHA3.get(segment.toLowerCase()) ?? segment;
|
|
260
|
+
return LANGUAGE_BY_NAME.get(segment.toLowerCase()) ?? segment;
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* Whether `value` puts a `|` beside a `+` that means AND, which no filter value can carry:
|
|
264
|
+
* OpenAlex splits a value on `|` first, and an alternative that ANDs more than one value matches
|
|
265
|
+
* nothing, so `DE|FR+US` answers with the German works alone. A `+` is no AND where
|
|
266
|
+
* `keepsPlusLiteral()`, nor inside a phrase key's value the caller wrapped in quotes, which
|
|
267
|
+
* OpenAlex reads as text split on `|`. Read from the caller's value before any conversion, so
|
|
268
|
+
* `Narnia|FR+US` fails for its operators, not its unknown country.
|
|
269
|
+
*/
|
|
270
|
+
function mixesOrWithPlus(field, value) {
|
|
271
|
+
if (!value.includes('|') || !value.includes('+') || keepsPlusLiteral(field))
|
|
272
|
+
return false;
|
|
273
|
+
return !(isPhraseKey(field) && isQuoted(value.trim()));
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* Whether a `|` alternative of `value` holds a space where a space between words is the AND
|
|
277
|
+
* operator: on an ID or code key, `A1 A2|A3` drops to `A1|A3` (or matches nothing for a code)
|
|
278
|
+
* exactly as `A1+A2|A3` would. Read from the value as converted, where a country or `language`
|
|
279
|
+
* name is already its code: `South Korea|Japan` goes out as `KR|JP`, a multi-word country segment
|
|
280
|
+
* that is no name has failed as `invalid_country_code` (`DE US|FR`), and only a segment sent as
|
|
281
|
+
* given keeps its space — a `language` segment that names no language (`en fr|de`) or a country
|
|
282
|
+
* path form. Never on a phrase key, whose multi-word value is sent wholly double-quoted (its spaces
|
|
283
|
+
* literal), nor where `keepsPlusLiteral()`.
|
|
284
|
+
*/
|
|
285
|
+
function mixesOrWithSpace(field, value) {
|
|
286
|
+
if (!value.includes('|') || keepsPlusLiteral(field) || isPhraseKey(field))
|
|
287
|
+
return false;
|
|
288
|
+
return value.split('|').some((alternative) => alternative.trim().includes(' '));
|
|
289
|
+
}
|
|
290
|
+
function mixedAndOrError(key) {
|
|
291
|
+
return validationError(`Filter ${quoteCallerText(key)} value mixes "|" (OR) with an AND operator — a "+" or a space — which one OpenAlex filter value cannot combine: OpenAlex splits the value on "|" first, and an alternative that ANDs more than one value matches nothing, so that alternative would be dropped without notice.`, { reason: 'mixed_and_or_in_filter_value', filterKey: key });
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* A filter value as its clause sends it. A value putting `|` beside an AND operator fails as
|
|
295
|
+
* `mixed_and_or_in_filter_value`: a `+` read from the caller's value (`mixesOrWithPlus()`), or a
|
|
296
|
+
* space on a key where words mean AND, read from the value as converted (`mixesOrWithSpace()`).
|
|
297
|
+
* On a country key or `language`, each `|`/`+` segment is trimmed and converted on its own, a
|
|
298
|
+
* leading `!` and the operators kept, so a multi-word name is one value (`South Korea|Japan` →
|
|
299
|
+
* `KR|JP`); upstream accepts a hyphenated key spelling, so `country-code` reads as
|
|
300
|
+
* `country_code`. A phrase key's multi-word value goes out as `quotedPhrase()` writes it. A value
|
|
301
|
+
* the caller wrapped in quotes goes out as written, its `+` a literal character. Otherwise, unless
|
|
302
|
+
* `keepsPlusLiteral()`, each `+` becomes a space: OpenAlex reads AND only from a decoded space — a
|
|
303
|
+
* raw `+` in the query string, which is what `URLSearchParams` sends for a space — and reads a
|
|
304
|
+
* `%2B` as a literal plus. Any value sent double-quoted is checked for a `"` of its own
|
|
305
|
+
* (`assertNoInteriorQuote()`).
|
|
306
|
+
*/
|
|
307
|
+
export function upstreamFilterValue(key, value) {
|
|
308
|
+
const field = key.replaceAll('-', '_');
|
|
309
|
+
if (mixesOrWithPlus(field, value))
|
|
310
|
+
throw mixedAndOrError(key);
|
|
311
|
+
const convert = COUNTRY_KEY_SUFFIXES.some((suffix) => field.endsWith(suffix))
|
|
312
|
+
? (segment) => countrySegment(segment, key)
|
|
313
|
+
: field === 'language'
|
|
314
|
+
? languageSegment
|
|
315
|
+
: undefined;
|
|
316
|
+
const converted = convert === undefined
|
|
317
|
+
? value
|
|
318
|
+
: value
|
|
319
|
+
.split(/([|+])/)
|
|
320
|
+
.map((part, index) => {
|
|
321
|
+
if (index % 2 === 1)
|
|
322
|
+
return part;
|
|
323
|
+
const trimmed = part.trim();
|
|
324
|
+
return trimmed.startsWith('!')
|
|
325
|
+
? `!${convert(trimmed.slice(1).trim())}`
|
|
326
|
+
: convert(trimmed);
|
|
327
|
+
})
|
|
328
|
+
.join('');
|
|
329
|
+
if (mixesOrWithSpace(field, converted))
|
|
330
|
+
throw mixedAndOrError(key);
|
|
331
|
+
const phrase = quotedPhrase(field, converted);
|
|
332
|
+
if (phrase !== undefined) {
|
|
333
|
+
assertNoInteriorQuote(key, phrase);
|
|
334
|
+
return phrase;
|
|
335
|
+
}
|
|
336
|
+
/**
|
|
337
|
+
* A value the caller wrapped in quotes goes out as written, its `+` a literal character, and
|
|
338
|
+
* carries no `"` beyond that pair. Free-text search values are the exception: OpenAlex reads a
|
|
339
|
+
* `"` there as its own exact-phrase syntax, so a search value keeps its quotes and is left as-is.
|
|
340
|
+
*/
|
|
341
|
+
if (!isFreeTextSearchKey(field) && isQuoted(converted.trim())) {
|
|
342
|
+
assertNoInteriorQuote(key, converted);
|
|
343
|
+
return converted;
|
|
344
|
+
}
|
|
345
|
+
assertPairedQuotes(key, converted);
|
|
346
|
+
if (keepsPlusLiteral(field))
|
|
347
|
+
return converted;
|
|
348
|
+
return converted.replaceAll('+', ' ');
|
|
349
|
+
}
|
|
350
|
+
//# sourceMappingURL=filter-values.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"filter-values.js","sourceRoot":"","sources":["../../../src/services/openalex/filter-values.ts"],"names":[],"mappings":"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"}
|