@cyanheads/openalex-mcp-server 0.8.0 → 0.9.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 (54) hide show
  1. package/AGENTS.md +20 -7
  2. package/CLAUDE.md +20 -7
  3. package/Dockerfile +17 -8
  4. package/README.md +20 -17
  5. package/changelog/0.9.x/0.9.0.md +48 -0
  6. package/dist/mcp-server/prompts/definitions/literature-review.prompt.js +1 -1
  7. package/dist/mcp-server/prompts/definitions/research-landscape.prompt.js +1 -1
  8. package/dist/mcp-server/tools/definitions/analyze-trends.tool.d.ts +27 -2
  9. package/dist/mcp-server/tools/definitions/analyze-trends.tool.d.ts.map +1 -1
  10. package/dist/mcp-server/tools/definitions/analyze-trends.tool.js +34 -4
  11. package/dist/mcp-server/tools/definitions/analyze-trends.tool.js.map +1 -1
  12. package/dist/mcp-server/tools/definitions/citation-graph.tool.d.ts +41 -4
  13. package/dist/mcp-server/tools/definitions/citation-graph.tool.d.ts.map +1 -1
  14. package/dist/mcp-server/tools/definitions/citation-graph.tool.js +62 -14
  15. package/dist/mcp-server/tools/definitions/citation-graph.tool.js.map +1 -1
  16. package/dist/mcp-server/tools/definitions/describe-fields.tool.d.ts.map +1 -1
  17. package/dist/mcp-server/tools/definitions/describe-fields.tool.js +34 -4
  18. package/dist/mcp-server/tools/definitions/describe-fields.tool.js.map +1 -1
  19. package/dist/mcp-server/tools/definitions/resolve-name.tool.d.ts +37 -1
  20. package/dist/mcp-server/tools/definitions/resolve-name.tool.d.ts.map +1 -1
  21. package/dist/mcp-server/tools/definitions/resolve-name.tool.js +175 -40
  22. package/dist/mcp-server/tools/definitions/resolve-name.tool.js.map +1 -1
  23. package/dist/mcp-server/tools/definitions/search-entities.tool.d.ts +41 -4
  24. package/dist/mcp-server/tools/definitions/search-entities.tool.d.ts.map +1 -1
  25. package/dist/mcp-server/tools/definitions/search-entities.tool.js +71 -23
  26. package/dist/mcp-server/tools/definitions/search-entities.tool.js.map +1 -1
  27. package/dist/mcp-server/tools/render-budget.d.ts +4 -2
  28. package/dist/mcp-server/tools/render-budget.d.ts.map +1 -1
  29. package/dist/mcp-server/tools/render-budget.js +4 -2
  30. package/dist/mcp-server/tools/render-budget.js.map +1 -1
  31. package/dist/mcp-server/tools/response-budget.d.ts +2 -1
  32. package/dist/mcp-server/tools/response-budget.d.ts.map +1 -1
  33. package/dist/mcp-server/tools/response-budget.js +30 -21
  34. package/dist/mcp-server/tools/response-budget.js.map +1 -1
  35. package/dist/services/openalex/budget.d.ts +3 -1
  36. package/dist/services/openalex/budget.d.ts.map +1 -1
  37. package/dist/services/openalex/budget.js +3 -1
  38. package/dist/services/openalex/budget.js.map +1 -1
  39. package/dist/services/openalex/field-catalog.json +50 -1
  40. package/dist/services/openalex/filter-values.d.ts +46 -0
  41. package/dist/services/openalex/filter-values.d.ts.map +1 -0
  42. package/dist/services/openalex/filter-values.js +294 -0
  43. package/dist/services/openalex/filter-values.js.map +1 -0
  44. package/dist/services/openalex/iso-codes.json +1565 -0
  45. package/dist/services/openalex/openalex-service.d.ts +172 -20
  46. package/dist/services/openalex/openalex-service.d.ts.map +1 -1
  47. package/dist/services/openalex/openalex-service.js +560 -120
  48. package/dist/services/openalex/openalex-service.js.map +1 -1
  49. package/dist/services/openalex/provider-text.d.ts +7 -6
  50. package/dist/services/openalex/provider-text.d.ts.map +1 -1
  51. package/dist/services/openalex/provider-text.js +485 -61
  52. package/dist/services/openalex/provider-text.js.map +1 -1
  53. package/package.json +14 -12
  54. package/server.json +10 -24
@@ -4,6 +4,38 @@
4
4
  */
5
5
  import type { Context } from '@cyanheads/mcp-ts-core';
6
6
  import { type AnalyzeParams, type AnalyzeResult, type AutocompleteParams, type AutocompleteRecord, type AutocompleteResult, type EntityRecord, type EntityType, type SearchParams, type SearchResult } from './types.js';
7
+ /** One upstream filter clause: the canonical key and the value it constrains. */
8
+ type FilterClause = [key: string, value: string];
9
+ /**
10
+ * Build an OpenAlex filter string from ordered key-value clauses.
11
+ * Input: [["cited_by_count", ">100"], ["is_oa", "true"]]
12
+ * Output: "cited_by_count:>100,is_oa:true"
13
+ *
14
+ * Clauses, not a record: OpenAlex ANDs a repeated key (`publication_year:2020,publication_year:2024`
15
+ * matches works in both years, which is nothing), and two caller filters can resolve to one
16
+ * canonical key. A record would hold only the last of them.
17
+ *
18
+ * Comma handling (commas collide with OpenAlex's filter clause separator):
19
+ * - Free-text search keys (`title.search`, `abstract.search.exact`, `semantic.search`, …) and
20
+ * keys matched as whole text (`raw_affiliation_strings`, `display_name`, `geo.city`, …): the
21
+ * value goes out double-quoted as `quotedCommaValue()` writes it, so the comma stays part of the
22
+ * phrase. The quoted value is sent as written, `+` included.
23
+ * - All other keys: throw a pre-flight validation error — OpenAlex OR-lists use `|`, not commas.
24
+ *
25
+ * Every other value goes out as `upstreamFilterValue()` writes it: country and `language` values
26
+ * converted to the codes OpenAlex stores (a country segment that cannot be a code throws
27
+ * `invalid_country_code`), a multi-word value on a key matched as whole text (`geo.city`,
28
+ * `awards.funder_display_name`, …) double-quoted so OpenAlex reads it as one phrase, and the AND
29
+ * operator — a `+`, or a space on a key where words mean AND — sent as the AND it spells. A value
30
+ * that puts that operator beside a `|` throws `mixed_and_or_in_filter_value`, since OpenAlex drops
31
+ * every `|` alternative that ANDs more than one value; a value sent double-quoted that carries a
32
+ * `"` of its own throws `quote_in_filter_value`. Every throw runs before any request.
33
+ * `autocomplete()` sends its filters without `translateFilters()`, so the conversion lives here
34
+ * rather than there.
35
+ *
36
+ * Exported for the timing suite, which runs it on caller-sized keys and values.
37
+ */
38
+ export declare function buildFilterString(filters: FilterClause[]): string;
7
39
  /**
8
40
  * Recovery for a PMCID that reached OpenAlex and came back with nothing. OpenAlex indexes no
9
41
  * PMCIDs — `has_pmcid:true` matches zero works and a work's `ids` object never carries the key —
@@ -12,6 +44,11 @@ import { type AnalyzeParams, type AnalyzeResult, type AutocompleteParams, type A
12
44
  * mapping here and the `openalex_resolve_name` miss notice so both say the same thing.
13
45
  */
14
46
  export declare const PMCID_NOT_INDEXED_HINT = "OpenAlex indexes no PMCIDs, so a PMCID resolves nothing however it is written. Convert it to a PMID or DOI \u2014 the NCBI ID Converter (https://www.ncbi.nlm.nih.gov/pmc/tools/idconv/) does this \u2014 and retry with that identifier.";
47
+ /**
48
+ * The filter clause that matches a funder by its bare DOI, or undefined for a value that clause
49
+ * would read as more than one literal DOI — it could only match some other funder, or every one.
50
+ */
51
+ export declare function funderDoiFilter(doi: string): string | undefined;
15
52
  /**
16
53
  * Detect ID format and return the API path segment.
17
54
  * "10.1038/nature12373" → "doi:10.1038/nature12373"
@@ -25,18 +62,51 @@ export declare const PMCID_NOT_INDEXED_HINT = "OpenAlex indexes no PMCIDs, so a
25
62
  * "https://www.ncbi.nlm.nih.gov/pmc/articles/PMC1234567/" → "pmcid:PMC1234567"
26
63
  * "https://pubmed.ncbi.nlm.nih.gov/21491125" → "pmid:21491125"
27
64
  * "PMID:21491125" → "pmid:21491125"
28
- * "https://openalex.org/keywords/groundwater" → "groundwater"
65
+ * "https://openalex.org/works/W2741809807" → "W2741809807"
66
+ * "https://api.openalex.org/keywords/groundwater" → "groundwater"
29
67
  * "W2741809807" → "W2741809807"
30
68
  */
31
69
  export declare function normalizeId(id: string): string;
70
+ /**
71
+ * The by-ID request path for `id`: `/<entityType>/<id>`, with every character a URL parse would
72
+ * read as structure percent-encoded so it stays part of the ID. OpenAlex decodes them back, so a
73
+ * DOI that holds a `#` or `?` reaches the record it names instead of a truncated one.
74
+ *
75
+ * A `.` or `..` segment, plain or percent-encoded, is refused before any request, since encoding
76
+ * cannot keep it inside the path. `/` is left as written, since DOIs carry it, and an encoded
77
+ * `%2F` is one segment to both the parse and OpenAlex. The refusal quotes `callerId`, the
78
+ * identifier as the caller wrote it, rather than the normalized `id` (`doi:…` for a DOI URL).
79
+ */
80
+ export declare function byIdPath(entityType: EntityType, id: string, callerId?: string): string;
32
81
  /** An identifier the deterministic by-ID path can resolve, with the scheme it was read as. */
33
82
  export interface ResolvedIdentifier {
34
- entityType: EntityType;
83
+ /** The entity types to look the identifier up as, in the order their records are returned. */
84
+ entityTypes: readonly EntityType[];
35
85
  /** The value in the form the OpenAlex path segment expects (`doi:10.1038/…`, `W2741809807`). */
36
86
  id: string;
87
+ /**
88
+ * Set on a bare native-ID shape, which short names share — keyword `p53` reads as publisher
89
+ * `P53`, and `F1`, `S1`, and `A1` are keywords too — so a lookup that finds no record is no
90
+ * sign the caller meant an ID, and the query is worth searching as a name next. A URL form
91
+ * names its record outright and never carries it.
92
+ */
93
+ mayBeName?: true;
37
94
  /** Identifier scheme, for caller-facing messages: `doi`, `orcid`, `openalex`, … */
38
95
  scheme: string;
39
96
  }
97
+ /** What `resolveIdentifier()` found. */
98
+ export interface IdentifierResolution extends AutocompleteResult {
99
+ /** Entity types whose lookup failed upstream while another type returned a record. */
100
+ failedEntityTypes?: EntityType[];
101
+ /**
102
+ * Set when every lookup found nothing and the query ran as a name search instead: `results`
103
+ * then hold that search's matches. `failedReason` names the upstream failure that stopped the
104
+ * search, with `results` empty.
105
+ */
106
+ nameFallback?: {
107
+ failedReason?: string;
108
+ };
109
+ }
40
110
  /**
41
111
  * Classify a query as an identifier, or return `undefined` for a name.
42
112
  *
@@ -45,12 +115,17 @@ export interface ResolvedIdentifier {
45
115
  * it produced. Names never survive that: a name with a colon in it yields a prefix that is in no
46
116
  * scheme table, and anything else fails the native-ID pattern.
47
117
  *
48
- * Keywords are the exception, and are read from the raw query instead: their IDs are slugs, so
49
- * `normalizeId()` reduces the URL to a bare word that no longer names an entity type. The URL
50
- * form is the only keyword spelling that identifies itself — a bare slug is indistinguishable
51
- * from any other name, and autocomplete is the right answer for it.
118
+ * Keywords are the exception, and are read from the URL before it is reduced: their IDs are
119
+ * slugs, so `normalizeId()` turns the URL into a bare word that no longer names an entity type.
120
+ * The URL form is the only keyword spelling that identifies itself — a bare slug is
121
+ * indistinguishable from any other name, and autocomplete is the right answer for it.
122
+ *
123
+ * `requested` (the caller's `entity_type`) narrows an identifier only among the types it can
124
+ * address: a ROR to one of institutions, funders, and publishers, and a Funder Registry DOI to
125
+ * works. Any other type leaves the identifier's own types in place — except `keywords`, under
126
+ * which a bare native-ID shape is a name: keyword IDs are slugs, and `P53` is a keyword.
52
127
  */
53
- export declare function inferIdentifier(query: string): ResolvedIdentifier | undefined;
128
+ export declare function inferIdentifier(query: string, requested?: EntityType): ResolvedIdentifier | undefined;
54
129
  /**
55
130
  * Shape a singleton entity record like an autocomplete match, so both resolution paths of
56
131
  * `openalex_resolve_name` return one result type. `entity_type` is singularized to match what
@@ -59,11 +134,27 @@ export declare function inferIdentifier(query: string): ResolvedIdentifier | und
59
134
  */
60
135
  export declare function toAutocompleteRecord(entityType: EntityType, record: EntityRecord): AutocompleteRecord;
61
136
  /**
62
- * The upstream field one caller-supplied `select` name projects to — `authors` → `authorships`
63
- * on works, `h_index` → `summary_stats` where that object exists; any other name unchanged.
64
- * Exported so a tool can find the record key a one-field projection lands under.
137
+ * The works `select` field the server derives rather than projects: each authorship's author as
138
+ * `{id, display_name, orcid}`, from the `authorships` it requests in its place. Not an upstream
139
+ * field, so it is never forwarded, and neither the default projection nor `*` includes it.
65
140
  */
66
- export declare function translateSelectField(entityType: SearchParams['entityType'], field: string): string;
141
+ export declare const AUTHOR_LIST_FIELD = "author_list";
142
+ /**
143
+ * The record key one caller-supplied `select` name lands under: `author_list` keeps its own
144
+ * name; any other name lands under the upstream field it projects (`authors` → `authorships`).
145
+ * Exported so a tool can find the array a one-field projection returns.
146
+ */
147
+ export declare function selectRecordKey(entityType: SearchParams['entityType'], field: string): string;
148
+ /**
149
+ * Detect a value the `id:` filter would carry — OpenAlex IDs, bare or in any URL spelling
150
+ * `parseOpenAlexUrl()` reads, optionally pipe-joined for OR semantics. When matched, `id` is
151
+ * rewritten to `openalex` (the canonical filter key) and the value is forwarded as written:
152
+ * upstream accepts every one of those spellings, and the space around a `|`. Stricter than any
153
+ * value so a future generic upstream `id:` filter wouldn't be shadowed — only values that read
154
+ * as OpenAlex IDs get redirected. Exported for the timing suite, which runs it on caller-sized
155
+ * values.
156
+ */
157
+ export declare function isOpenAlexFilterValue(value: string): boolean;
67
158
  /**
68
159
  * Rewrite one caller-supplied filter key to the upstream OpenAlex name, before the
69
160
  * value-dependent `id` rewrite. Exported so the citation-graph tool can check a caller's key
@@ -74,6 +165,20 @@ export declare function translateSelectField(entityType: SearchParams['entityTyp
74
165
  * typos no map can preempt.
75
166
  */
76
167
  export declare function translateFilterKey(entityType: SearchParams['entityType'], key: string): string;
168
+ /**
169
+ * The `filter` parameter a search or group_by over `filters` sends: keys translated, then each
170
+ * clause built by `buildFilterString()`, whose pre-flight throws (`comma_in_filter_value`,
171
+ * `invalid_country_code`, `mixed_and_or_in_filter_value`, `quote_in_filter_value`) it shares.
172
+ * Exported so a tool that spends a request before its search — the citation graph's seed lookup —
173
+ * can run those checks first and fail without one.
174
+ */
175
+ export declare function buildSearchFilter(entityType: SearchParams['entityType'], filters: Record<string, string>): string;
176
+ /**
177
+ * Recovery for every `upstream_unavailable` failure of a semantic search — a relabelled embedding
178
+ * failure, the 503 OpenAlex sends when the embedding timed out, or any other outage — since a
179
+ * keyword search does not depend on the embedding service and can answer while it is down.
180
+ */
181
+ export declare const SEMANTIC_UNAVAILABLE_HINT = "OpenAlex semantic search is failing upstream. Retry in a minute, or rerun with search_mode \"keyword\", which does not depend on it.";
77
182
  /**
78
183
  * Recovery for a spent daily budget on a server that sends no API key. It runs on the smaller
79
184
  * anonymous budget, so a key is the one fix besides waiting. A keyed server's budget is already
@@ -81,21 +186,40 @@ export declare function translateFilterKey(entityType: SearchParams['entityType'
81
186
  * case keeps the key-neutral recovery each tool's contract declares.
82
187
  */
83
188
  export declare const BUDGET_EXHAUSTED_KEYLESS_HINT = "This server's daily OpenAlex budget is spent and refills at midnight UTC \u2014 retrying before then will not succeed. The server sends no OpenAlex API key, so it runs on the smaller anonymous budget; setting OPENALEX_API_KEY to a free key (https://openalex.org/settings/api) raises it.";
189
+ /**
190
+ * The server's own message for a plan-gated filter, naming the filter when the upstream message
191
+ * opens with it. The name is read as a field-name run only, so caller text the upstream clause
192
+ * echoes cannot carry markdown or a line break into the message.
193
+ */
194
+ export declare function planGatedFilterMessage(upstreamMessage: string | undefined): string;
84
195
  declare class OpenAlexService {
85
196
  private readonly baseUrl;
86
197
  private readonly apiKey;
87
198
  private readonly mailto;
88
199
  constructor();
89
- /** Execute an HTTP request against the OpenAlex API with retry on transient failures. */
200
+ /**
201
+ * Execute an HTTP request against the OpenAlex API with retry on transient failures. With
202
+ * `deadlineMs`, every attempt and backoff settles within that many milliseconds, after which
203
+ * the request fails as a `Timeout`.
204
+ */
90
205
  private request;
91
206
  private throwNormalizedRequestError;
92
207
  private parseResponse;
93
208
  /** Search/filter/sort entities, or retrieve a single entity by ID. */
94
209
  search(params: SearchParams, ctx: Context): Promise<SearchResult>;
210
+ /**
211
+ * Look a funder up by its Crossref Funder Registry DOI. OpenAlex's `/funders/doi:` path
212
+ * resolves nothing, so this is a one-result list request on the `ids.doi` filter, which OpenAlex
213
+ * bills like any list request. A value the filter would read as more than one literal DOI fails
214
+ * as not found without a request (see `funderDoiFilter`).
215
+ */
216
+ private funderByDoi;
95
217
  /** Group-by aggregation. */
96
218
  analyze(params: AnalyzeParams, ctx: Context): Promise<AnalyzeResult>;
97
219
  /**
98
- * Resolve an identifier to the single entity it addresses, shaped as an autocomplete match.
220
+ * Resolve an identifier to the entities it addresses, shaped as autocomplete matches — one
221
+ * lookup per entity type in `identifier.entityTypes`, run in parallel, with the records found
222
+ * returned in that order.
99
223
  *
100
224
  * Autocomplete matches on `display_name` and the external-ID URL, so whether an identifier
101
225
  * resolves through it depends on scheme, bare-vs-URL form, and entity type — a bare ORCID or
@@ -106,22 +230,50 @@ declare class OpenAlexService {
106
230
  * A miss returns no results rather than throwing. Autocomplete answers an unmatched query with
107
231
  * an empty list, and routing identifiers through here is meant to make them behave *more* like
108
232
  * the front door, not to hand one class of query a hard failure the rest never sees.
233
+ *
234
+ * A lookup that fails for any other reason drops only its own type, named in
235
+ * `failedEntityTypes`, while another type returned a record. With no record to return, or once
236
+ * the caller has cancelled, the failure is thrown instead — an empty or partial answer there
237
+ * would claim records do not exist when the lookup for them never finished.
238
+ *
239
+ * `nameSearch` is the autocomplete to run when every lookup finds nothing — for a query that
240
+ * may be a name rather than an ID (`ResolvedIdentifier.mayBeName`). It runs only on that miss,
241
+ * so an ID that resolves still costs its one request, and only within what is left of
242
+ * `NAME_FALLBACK_DEADLINE_MS` from the start of this call (see `nameSearchAfterMiss`).
243
+ *
244
+ * `query` is the text `identifier` was read from, as the caller wrote it. The lookups are sent
245
+ * that spelling, which `search()` normalizes back to `identifier.id`, so a lookup that refuses
246
+ * the identifier quotes what the caller sent.
247
+ */
248
+ resolveIdentifier(identifier: ResolvedIdentifier, ctx: Context, nameSearch?: AutocompleteParams, query?: string): Promise<IdentifierResolution>;
249
+ /**
250
+ * The name search an identifier lookup falls back to after finding nothing, bounded by the
251
+ * `remainingMs` the lookup left of the call's deadline. It is a best-effort second reading of
252
+ * the query, so a transient upstream failure — a timeout, the deadline running out, an outage,
253
+ * a burst throttle — comes back as `nameFallback.failedReason` for the caller to see, beside
254
+ * the lookup's own true miss. A caller cancellation, a request upstream rejected, a spent
255
+ * budget, and anything unclassified are thrown: none of them is cured by calling again, and an
256
+ * answer in their place would hide the error the caller needs to act on.
109
257
  */
110
- resolveIdentifier(identifier: ResolvedIdentifier, ctx: Context): Promise<AutocompleteResult>;
111
- /** Autocomplete name resolution. */
112
- autocomplete(params: AutocompleteParams, ctx: Context): Promise<AutocompleteResult>;
258
+ private nameSearchAfterMiss;
259
+ /**
260
+ * Autocomplete name resolution. `deadlineMs` bounds the whole request, retries included (see
261
+ * `request`).
262
+ */
263
+ autocomplete(params: AutocompleteParams, ctx: Context, deadlineMs?: number): Promise<AutocompleteResult>;
113
264
  }
114
265
  export declare function initOpenAlexService(): void;
115
266
  export declare function getOpenAlexService(): OpenAlexService;
267
+ type FieldCatalog = Record<EntityType, {
268
+ filter: string[];
269
+ select: string[];
270
+ }>;
116
271
  /**
117
272
  * Return the typed field catalog keyed by entity type → context → field list.
118
273
  * Only `filter` and `select` arrays are stored. `group_by` resolves to the `filter` key here;
119
274
  * the describe-fields handler then prunes the fields group_by cannot target (raw dates,
120
275
  * `*.search` operators, `from_*`/`to_*` range modifiers, and a per-type set from a live sweep).
121
276
  */
122
- export declare function getFieldCatalog(): Record<EntityType, {
123
- filter: string[];
124
- select: string[];
125
- }>;
277
+ export declare function getFieldCatalog(): FieldCatalog;
126
278
  export {};
127
279
  //# sourceMappingURL=openalex-service.d.ts.map
@@ -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;AAuBtD,OAAO,EACL,KAAK,aAAa,EAClB,KAAK,aAAa,EAClB,KAAK,kBAAkB,EACvB,KAAK,kBAAkB,EACvB,KAAK,kBAAkB,EAGvB,KAAK,YAAY,EACjB,KAAK,UAAU,EAEf,KAAK,YAAY,EACjB,KAAK,YAAY,EAClB,MAAM,YAAY,CAAC;AAoFpB;;;;;;GAMG;AACH,eAAO,MAAM,sBAAsB,8OACgM,CAAC;AA0BpO;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAuF9C;AAuBD,8FAA8F;AAC9F,MAAM,WAAW,kBAAkB;IACjC,UAAU,EAAE,UAAU,CAAC;IACvB,gGAAgG;IAChG,EAAE,EAAE,MAAM,CAAC;IACX,mFAAmF;IACnF,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,kBAAkB,GAAG,SAAS,CAkB7E;AA4HD;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAClC,UAAU,EAAE,UAAU,EACtB,MAAM,EAAE,YAAY,GACnB,kBAAkB,CAUpB;AA2ED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAClC,UAAU,EAAE,YAAY,CAAC,YAAY,CAAC,EACtC,KAAK,EAAE,MAAM,GACZ,MAAM,CAQR;AA+CD;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,YAAY,CAAC,YAAY,CAAC,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAE9F;AAqSD;;;;;GAKG;AACH,eAAO,MAAM,6BAA6B,mSACmP,CAAC;AAsJ9R,cAAM,eAAe;IACnB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAEhC,cAKC;IAED,yFAAyF;IACzF,OAAO,CAAC,OAAO;IAgDf,OAAO,CAAC,2BAA2B;IAgGnC,OAAO,CAAC,aAAa;IAqCrB,sEAAsE;IAChE,MAAM,CAAC,MAAM,EAAE,YAAY,EAAE,GAAG,EAAE,OAAO,GAAG,OAAO,CAAC,YAAY,CAAC,CA6FtE;IAED,4BAA4B;IACtB,OAAO,CAAC,MAAM,EAAE,aAAa,EAAE,GAAG,EAAE,OAAO,GAAG,OAAO,CAAC,aAAa,CAAC,CA2DzE;IAED;;;;;;;;;;;;OAYG;IACG,iBAAiB,CACrB,UAAU,EAAE,kBAAkB,EAC9B,GAAG,EAAE,OAAO,GACX,OAAO,CAAC,kBAAkB,CAAC,CAkB7B;IAED,oCAAoC;IAC9B,YAAY,CAAC,MAAM,EAAE,kBAAkB,EAAE,GAAG,EAAE,OAAO,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAsBxF;CACF;AAID,wBAAgB,mBAAmB,IAAI,IAAI,CAE1C;AAED,wBAAgB,kBAAkB,IAAI,eAAe,CAIpD;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,IAAI,MAAM,CAAC,UAAU,EAAE;IAAE,MAAM,EAAE,MAAM,EAAE,CAAC;IAAC,MAAM,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC,CAE5F"}
1
+ {"version":3,"file":"openalex-service.d.ts","sourceRoot":"","sources":["../../../src/services/openalex/openalex-service.ts"],"names":[],"mappings":"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"}