@de-otio/bibcheck 0.1.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/LICENSE +21 -0
- package/README.md +147 -0
- package/dist/cache/fs-cache.d.ts +55 -0
- package/dist/cache/fs-cache.d.ts.map +1 -0
- package/dist/cache/fs-cache.js +264 -0
- package/dist/cache/fs-cache.js.map +1 -0
- package/dist/canonical.d.ts +29 -0
- package/dist/canonical.d.ts.map +1 -0
- package/dist/canonical.js +132 -0
- package/dist/canonical.js.map +1 -0
- package/dist/check.d.ts +140 -0
- package/dist/check.d.ts.map +1 -0
- package/dist/check.js +646 -0
- package/dist/check.js.map +1 -0
- package/dist/cli.d.ts +19 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +357 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +175 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +180 -0
- package/dist/config.js.map +1 -0
- package/dist/databases/crossref.d.ts +53 -0
- package/dist/databases/crossref.d.ts.map +1 -0
- package/dist/databases/crossref.js +138 -0
- package/dist/databases/crossref.js.map +1 -0
- package/dist/databases/index.d.ts +12 -0
- package/dist/databases/index.d.ts.map +1 -0
- package/dist/databases/index.js +9 -0
- package/dist/databases/index.js.map +1 -0
- package/dist/databases/openalex.d.ts +29 -0
- package/dist/databases/openalex.d.ts.map +1 -0
- package/dist/databases/openalex.js +117 -0
- package/dist/databases/openalex.js.map +1 -0
- package/dist/databases/openlibrary.d.ts +26 -0
- package/dist/databases/openlibrary.d.ts.map +1 -0
- package/dist/databases/openlibrary.js +79 -0
- package/dist/databases/openlibrary.js.map +1 -0
- package/dist/databases/worldcat.d.ts +33 -0
- package/dist/databases/worldcat.d.ts.map +1 -0
- package/dist/databases/worldcat.js +145 -0
- package/dist/databases/worldcat.js.map +1 -0
- package/dist/doctor.d.ts +44 -0
- package/dist/doctor.d.ts.map +1 -0
- package/dist/doctor.js +386 -0
- package/dist/doctor.js.map +1 -0
- package/dist/existence.d.ts +70 -0
- package/dist/existence.d.ts.map +1 -0
- package/dist/existence.js +308 -0
- package/dist/existence.js.map +1 -0
- package/dist/http.d.ts +97 -0
- package/dist/http.d.ts.map +1 -0
- package/dist/http.js +543 -0
- package/dist/http.js.map +1 -0
- package/dist/identifiers.d.ts +44 -0
- package/dist/identifiers.d.ts.map +1 -0
- package/dist/identifiers.js +111 -0
- package/dist/identifiers.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/linkage.d.ts +29 -0
- package/dist/linkage.d.ts.map +1 -0
- package/dist/linkage.js +73 -0
- package/dist/linkage.js.map +1 -0
- package/dist/markdown/blocks.d.ts +19 -0
- package/dist/markdown/blocks.d.ts.map +1 -0
- package/dist/markdown/blocks.js +69 -0
- package/dist/markdown/blocks.js.map +1 -0
- package/dist/markdown/citekeys.d.ts +22 -0
- package/dist/markdown/citekeys.d.ts.map +1 -0
- package/dist/markdown/citekeys.js +100 -0
- package/dist/markdown/citekeys.js.map +1 -0
- package/dist/markdown/glob.d.ts +18 -0
- package/dist/markdown/glob.d.ts.map +1 -0
- package/dist/markdown/glob.js +26 -0
- package/dist/markdown/glob.js.map +1 -0
- package/dist/markdown/prose.d.ts +19 -0
- package/dist/markdown/prose.d.ts.map +1 -0
- package/dist/markdown/prose.js +81 -0
- package/dist/markdown/prose.js.map +1 -0
- package/dist/output/json.d.ts +21 -0
- package/dist/output/json.d.ts.map +1 -0
- package/dist/output/json.js +24 -0
- package/dist/output/json.js.map +1 -0
- package/dist/output/markdown.d.ts +21 -0
- package/dist/output/markdown.d.ts.map +1 -0
- package/dist/output/markdown.js +194 -0
- package/dist/output/markdown.js.map +1 -0
- package/dist/output/sarif.d.ts +31 -0
- package/dist/output/sarif.d.ts.map +1 -0
- package/dist/output/sarif.js +322 -0
- package/dist/output/sarif.js.map +1 -0
- package/dist/output/text.d.ts +27 -0
- package/dist/output/text.d.ts.map +1 -0
- package/dist/output/text.js +212 -0
- package/dist/output/text.js.map +1 -0
- package/dist/phrases/load.d.ts +34 -0
- package/dist/phrases/load.d.ts.map +1 -0
- package/dist/phrases/load.js +148 -0
- package/dist/phrases/load.js.map +1 -0
- package/dist/phrases.d.ts +27 -0
- package/dist/phrases.d.ts.map +1 -0
- package/dist/phrases.js +116 -0
- package/dist/phrases.js.map +1 -0
- package/dist/schema/csl.d.ts +429 -0
- package/dist/schema/csl.d.ts.map +1 -0
- package/dist/schema/csl.js +101 -0
- package/dist/schema/csl.js.map +1 -0
- package/dist/schema/output.d.ts +1116 -0
- package/dist/schema/output.d.ts.map +1 -0
- package/dist/schema/output.js +419 -0
- package/dist/schema/output.js.map +1 -0
- package/dist/suppression.d.ts +106 -0
- package/dist/suppression.d.ts.map +1 -0
- package/dist/suppression.js +134 -0
- package/dist/suppression.js.map +1 -0
- package/dist/version.d.ts +11 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +14 -0
- package/dist/version.js.map +1 -0
- package/dist/worklist.d.ts +32 -0
- package/dist/worklist.d.ts.map +1 -0
- package/dist/worklist.js +211 -0
- package/dist/worklist.js.map +1 -0
- package/package.json +82 -0
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Existence subcommand — commodity convenience layer.
|
|
3
|
+
*
|
|
4
|
+
* For each CSL-JSON bibliography entry, performs DOI/ISBN/title-search
|
|
5
|
+
* lookups against CrossRef, OpenAlex, and OpenLibrary, then assigns an
|
|
6
|
+
* ExistenceStatus based on the best result across all tried sources. Each
|
|
7
|
+
* layer also carries the Q2 evidence vocabulary (`evidence` / `checkedFor` /
|
|
8
|
+
* `notCheckedFor`) and a sanitized `error` string when a source crashed.
|
|
9
|
+
*
|
|
10
|
+
* Title metadata-match rule (H3): a hand-rolled token-set ratio over the
|
|
11
|
+
* normalised title tokens, so subtitle presence ("Liberty" vs "Liberty: A
|
|
12
|
+
* Study") and word reordering do not cause a false metadata-mismatch.
|
|
13
|
+
* Normalised Levenshtein is kept only as a tiebreaker for near-identical
|
|
14
|
+
* titles where the token sets diverge. Ratio ≥ 0.85 → match.
|
|
15
|
+
*
|
|
16
|
+
* First-author match:
|
|
17
|
+
* Case-insensitive substring of first-author surname in any metadata
|
|
18
|
+
* author string.
|
|
19
|
+
*
|
|
20
|
+
* Identifier short-circuit (T21/T22): a malformed or bad-checksum DOI/ISBN is
|
|
21
|
+
* a strong, cheap fabrication signal and cannot be looked up; callers pass
|
|
22
|
+
* `identifierInvalid` so the network call is skipped and the entry is recorded
|
|
23
|
+
* as `unverifiable` with a note (it is already gating via
|
|
24
|
+
* `summary.malformedIdentifiers`).
|
|
25
|
+
*
|
|
26
|
+
* WorldCat (OCLC Classify) was removed in 0.2.0 — the endpoint was
|
|
27
|
+
* decommissioned in 2019 (see tmp/design-review/worldcat.md). ISBN existence
|
|
28
|
+
* is covered by OpenLibrary.
|
|
29
|
+
*/
|
|
30
|
+
import { distance } from 'fastest-levenshtein';
|
|
31
|
+
import { HttpError } from './http.js';
|
|
32
|
+
// ---------------------------------------------------------------------------
|
|
33
|
+
// Author helpers
|
|
34
|
+
// ---------------------------------------------------------------------------
|
|
35
|
+
export function getFirstAuthorSurname(entry) {
|
|
36
|
+
const first = entry.author?.[0];
|
|
37
|
+
if (first === undefined)
|
|
38
|
+
return undefined;
|
|
39
|
+
return first.family ?? first.literal?.split(/\s+/).pop();
|
|
40
|
+
}
|
|
41
|
+
export function getAllAuthorNames(entry) {
|
|
42
|
+
return (entry.author ?? []).map((a) => a.family ?? a.literal ?? '').filter((s) => s.length > 0);
|
|
43
|
+
}
|
|
44
|
+
// ---------------------------------------------------------------------------
|
|
45
|
+
// Title normalisation and fuzzy match (H3: token-set ratio)
|
|
46
|
+
// ---------------------------------------------------------------------------
|
|
47
|
+
/**
|
|
48
|
+
* Normalise a title for comparison: lowercase, strip non-alphanumeric runs
|
|
49
|
+
* (punctuation + whitespace), collapse to single spaces.
|
|
50
|
+
*/
|
|
51
|
+
function normaliseTitle(t) {
|
|
52
|
+
return t
|
|
53
|
+
.toLowerCase()
|
|
54
|
+
.replace(/[^a-z0-9]+/g, ' ')
|
|
55
|
+
.trim();
|
|
56
|
+
}
|
|
57
|
+
/** Split a normalised title into a de-duplicated set of word tokens. */
|
|
58
|
+
function tokenSet(t) {
|
|
59
|
+
const norm = normaliseTitle(t);
|
|
60
|
+
if (norm === '')
|
|
61
|
+
return new Set();
|
|
62
|
+
return new Set(norm.split(' '));
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Token-set ratio: |intersection| / |smaller set|. This is deliberately
|
|
66
|
+
* asymmetric in favour of subset relationships — when one title is a subset of
|
|
67
|
+
* the other (e.g. "Liberty" ⊂ "Liberty A Study"), every token of the smaller
|
|
68
|
+
* set is present, so the ratio is 1.0 and the titles match. Word reordering
|
|
69
|
+
* has no effect because sets are unordered.
|
|
70
|
+
*/
|
|
71
|
+
function tokenSetRatio(a, b) {
|
|
72
|
+
const smaller = a.size <= b.size ? a : b;
|
|
73
|
+
const larger = a.size <= b.size ? b : a;
|
|
74
|
+
if (smaller.size === 0)
|
|
75
|
+
return larger.size === 0 ? 1 : 0;
|
|
76
|
+
let inter = 0;
|
|
77
|
+
for (const tok of smaller) {
|
|
78
|
+
if (larger.has(tok))
|
|
79
|
+
inter += 1;
|
|
80
|
+
}
|
|
81
|
+
return inter / smaller.size;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Returns true if the two title strings are close enough to be the same work.
|
|
85
|
+
*
|
|
86
|
+
* Primary metric is the token-set ratio (≥ 0.85), which is robust to subtitle
|
|
87
|
+
* presence and word reordering. As a tiebreaker (e.g. single-token titles with
|
|
88
|
+
* a small typo, where the token sets are disjoint but the strings are close),
|
|
89
|
+
* the normalised Levenshtein ratio (≥ 0.85) is consulted.
|
|
90
|
+
*/
|
|
91
|
+
export function titlesMatch(a, b) {
|
|
92
|
+
const na = normaliseTitle(a);
|
|
93
|
+
const nb = normaliseTitle(b);
|
|
94
|
+
if (na === nb)
|
|
95
|
+
return true;
|
|
96
|
+
const setA = tokenSet(a);
|
|
97
|
+
const setB = tokenSet(b);
|
|
98
|
+
if (tokenSetRatio(setA, setB) >= 0.85)
|
|
99
|
+
return true;
|
|
100
|
+
// Levenshtein tiebreaker: catches near-identical titles whose tokenisation
|
|
101
|
+
// diverges (typos, hyphenation differences across single tokens).
|
|
102
|
+
const maxLen = Math.max(na.length, nb.length);
|
|
103
|
+
if (maxLen === 0)
|
|
104
|
+
return true;
|
|
105
|
+
const d = distance(na, nb);
|
|
106
|
+
const ratio = 1 - d / maxLen;
|
|
107
|
+
return ratio >= 0.85;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Returns true if the first-author surname of the entry appears (case-
|
|
111
|
+
* insensitively) as a substring in at least one of the metadata author strings.
|
|
112
|
+
*/
|
|
113
|
+
function authorsMatch(surname, metaAuthors) {
|
|
114
|
+
const lower = surname.toLowerCase();
|
|
115
|
+
return metaAuthors.some((a) => a.toLowerCase().includes(lower));
|
|
116
|
+
}
|
|
117
|
+
function compareMetadata(entry, meta) {
|
|
118
|
+
if (meta === null)
|
|
119
|
+
return 'metadata-mismatch';
|
|
120
|
+
const entryTitle = entry.title;
|
|
121
|
+
const metaTitle = meta.title;
|
|
122
|
+
const titleOk = entryTitle === undefined || metaTitle === undefined || titlesMatch(entryTitle, metaTitle);
|
|
123
|
+
const surname = getFirstAuthorSurname(entry);
|
|
124
|
+
const metaAuthors = meta.authors ?? [];
|
|
125
|
+
const authorOk = surname === undefined || metaAuthors.length === 0 || authorsMatch(surname, metaAuthors);
|
|
126
|
+
return titleOk && authorOk ? 'found' : 'metadata-mismatch';
|
|
127
|
+
}
|
|
128
|
+
// ---------------------------------------------------------------------------
|
|
129
|
+
// Per-source check builders
|
|
130
|
+
// ---------------------------------------------------------------------------
|
|
131
|
+
function lookupResultToCheck(source, entry, lookup) {
|
|
132
|
+
if (!lookup.found) {
|
|
133
|
+
return { source, result: 'not-found', evidence: null };
|
|
134
|
+
}
|
|
135
|
+
const matchResult = compareMetadata(entry, lookup.metadata);
|
|
136
|
+
return {
|
|
137
|
+
source,
|
|
138
|
+
result: matchResult,
|
|
139
|
+
evidence: lookup.metadata ?? null,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Strip `mailto=<value>` query params from any URL embedded in a free-text
|
|
144
|
+
* error message, so the polite-pool email never reaches the output. We cannot
|
|
145
|
+
* rely on `sanitizeMailto` here because that only rewrites strings that ARE a
|
|
146
|
+
* URL; an HttpError message like "GET https://…?mailto=x failed" is a sentence
|
|
147
|
+
* with a URL inside it.
|
|
148
|
+
*/
|
|
149
|
+
function sanitizeErrorMessage(message) {
|
|
150
|
+
return message.replace(/([?&])mailto=[^&\s]*/gi, (_m, sep) => sep === '?' ? '?' : '');
|
|
151
|
+
}
|
|
152
|
+
async function safeCall(source, fn, entry) {
|
|
153
|
+
try {
|
|
154
|
+
const result = await fn();
|
|
155
|
+
return lookupResultToCheck(source, entry, result);
|
|
156
|
+
}
|
|
157
|
+
catch (err) {
|
|
158
|
+
if (err instanceof HttpError) {
|
|
159
|
+
// Sanitize so a ?mailto= / API key in the message cannot leak into output.
|
|
160
|
+
const message = sanitizeErrorMessage(err.message);
|
|
161
|
+
return { source, result: 'error', evidence: { error: message } };
|
|
162
|
+
}
|
|
163
|
+
throw err;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
// ---------------------------------------------------------------------------
|
|
167
|
+
// Status derivation
|
|
168
|
+
// ---------------------------------------------------------------------------
|
|
169
|
+
function deriveStatus(checks) {
|
|
170
|
+
const results = new Set(checks.map((c) => c.result));
|
|
171
|
+
if (results.has('found'))
|
|
172
|
+
return 'verified';
|
|
173
|
+
if (results.has('metadata-mismatch'))
|
|
174
|
+
return 'metadata-mismatch';
|
|
175
|
+
if (results.has('not-found')) {
|
|
176
|
+
// Only 'not-found' (and possibly 'error') entries — but at least one confirmed absence.
|
|
177
|
+
if ([...results].every((r) => r === 'not-found' || r === 'error')) {
|
|
178
|
+
// If there is at least one 'not-found', report that even if some errored.
|
|
179
|
+
return 'not-found-in-databases';
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
// All 'error' or all 'no-doi'.
|
|
183
|
+
return 'unverifiable';
|
|
184
|
+
}
|
|
185
|
+
// ---------------------------------------------------------------------------
|
|
186
|
+
// Evidence vocabulary (Q2)
|
|
187
|
+
// ---------------------------------------------------------------------------
|
|
188
|
+
/**
|
|
189
|
+
* Map an existence status to its evidence vocabulary: the discrete
|
|
190
|
+
* evidence level plus the explicit checkedFor / notCheckedFor dimensions.
|
|
191
|
+
* `claim-support` is ALWAYS in notCheckedFor (bibcheck never verifies whether
|
|
192
|
+
* the source supports the prose's claim — that is the manual worklist's job).
|
|
193
|
+
*/
|
|
194
|
+
const ALL_DIMENSIONS = [
|
|
195
|
+
'existence',
|
|
196
|
+
'metadata',
|
|
197
|
+
'canonical-url',
|
|
198
|
+
'claim-support',
|
|
199
|
+
];
|
|
200
|
+
function evidenceFor(status) {
|
|
201
|
+
switch (status) {
|
|
202
|
+
case 'verified':
|
|
203
|
+
return { evidence: 'exists-metadata-match', checkedFor: ['existence', 'metadata'] };
|
|
204
|
+
case 'metadata-mismatch':
|
|
205
|
+
return { evidence: 'exists-metadata-mismatch', checkedFor: ['existence', 'metadata'] };
|
|
206
|
+
case 'not-found-in-databases':
|
|
207
|
+
return { evidence: 'absent', checkedFor: ['existence'] };
|
|
208
|
+
case 'unverifiable':
|
|
209
|
+
return { evidence: 'unverifiable', checkedFor: [] };
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Build a complete ExistenceLayer from the per-source checks: derive the
|
|
214
|
+
* status, attach the evidence vocabulary, and surface a sanitized top-level
|
|
215
|
+
* error when EVERY check crashed (vs. a clean unverifiable result).
|
|
216
|
+
*/
|
|
217
|
+
function buildLayer(checks) {
|
|
218
|
+
const status = deriveStatus(checks);
|
|
219
|
+
const { evidence, checkedFor } = evidenceFor(status);
|
|
220
|
+
const notCheckedFor = ALL_DIMENSIONS.filter((d) => !checkedFor.includes(d));
|
|
221
|
+
// Surface a top-level error only when the layer failed to run cleanly: all
|
|
222
|
+
// checks were transport errors. (A mix of error + not-found is a real
|
|
223
|
+
// not-found-in-databases result and is not flagged as an error.)
|
|
224
|
+
let error = null;
|
|
225
|
+
if (checks.length > 0 && checks.every((c) => c.result === 'error')) {
|
|
226
|
+
const first = checks.find((c) => c.result === 'error');
|
|
227
|
+
const ev = first?.evidence;
|
|
228
|
+
if (ev !== null && typeof ev === 'object' && 'error' in ev) {
|
|
229
|
+
error = String(ev.error);
|
|
230
|
+
}
|
|
231
|
+
else {
|
|
232
|
+
error = 'All existence sources failed.';
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
return { status, evidence, checkedFor, notCheckedFor, checks, error };
|
|
236
|
+
}
|
|
237
|
+
// ---------------------------------------------------------------------------
|
|
238
|
+
// Per-entry processing
|
|
239
|
+
// ---------------------------------------------------------------------------
|
|
240
|
+
/**
|
|
241
|
+
* Build the unverifiable layer for an entry whose local identifier validation
|
|
242
|
+
* (T21) failed. The network existence call is skipped entirely.
|
|
243
|
+
*/
|
|
244
|
+
function identifierInvalidLayer() {
|
|
245
|
+
const checks = [
|
|
246
|
+
{
|
|
247
|
+
source: 'crossref',
|
|
248
|
+
result: 'error',
|
|
249
|
+
evidence: { error: 'Identifier validation failed; existence lookup skipped.' },
|
|
250
|
+
},
|
|
251
|
+
];
|
|
252
|
+
const { evidence, checkedFor } = evidenceFor('unverifiable');
|
|
253
|
+
const notCheckedFor = ALL_DIMENSIONS.filter((d) => !checkedFor.includes(d));
|
|
254
|
+
return {
|
|
255
|
+
status: 'unverifiable',
|
|
256
|
+
evidence,
|
|
257
|
+
checkedFor,
|
|
258
|
+
notCheckedFor,
|
|
259
|
+
checks,
|
|
260
|
+
error: 'Identifier validation failed; existence lookup skipped.',
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
async function processEntry(entry, clients, signal) {
|
|
264
|
+
const checks = [];
|
|
265
|
+
if (entry.doi !== undefined && entry.doi !== '') {
|
|
266
|
+
// DOI route: CrossRef + OpenAlex in parallel.
|
|
267
|
+
const [crCheck, oaCheck] = await Promise.all([
|
|
268
|
+
safeCall('crossref', () => clients.crossref.lookupByDoi(entry.doi, signal), entry),
|
|
269
|
+
safeCall('openalex', () => clients.openalex.lookupByDoi(entry.doi, signal), entry),
|
|
270
|
+
]);
|
|
271
|
+
checks.push(crCheck, oaCheck);
|
|
272
|
+
}
|
|
273
|
+
else if (entry.isbn !== undefined && entry.isbn !== '') {
|
|
274
|
+
// ISBN route: OpenLibrary (the keyless ISBN source; WorldCat removed).
|
|
275
|
+
const olCheck = await safeCall('openlibrary', () => clients.openlibrary.lookupByIsbn(entry.isbn, signal), entry);
|
|
276
|
+
checks.push(olCheck);
|
|
277
|
+
}
|
|
278
|
+
else if (entry.title !== undefined && entry.title !== '') {
|
|
279
|
+
// Title-search route: OpenAlex only.
|
|
280
|
+
const authors = getAllAuthorNames(entry);
|
|
281
|
+
const oaCheck = await safeCall('openalex', () => clients.openalex.searchByTitleAuthor(entry.title, authors, signal), entry);
|
|
282
|
+
checks.push(oaCheck);
|
|
283
|
+
}
|
|
284
|
+
else {
|
|
285
|
+
// No identifier — unverifiable.
|
|
286
|
+
checks.push({ source: 'crossref', result: 'no-doi', evidence: null });
|
|
287
|
+
}
|
|
288
|
+
return buildLayer(checks);
|
|
289
|
+
}
|
|
290
|
+
// ---------------------------------------------------------------------------
|
|
291
|
+
// runExistence
|
|
292
|
+
// ---------------------------------------------------------------------------
|
|
293
|
+
export async function runExistence(deps) {
|
|
294
|
+
const { bibliography, clients, identifierInvalid, signal } = deps;
|
|
295
|
+
const entries = [];
|
|
296
|
+
for (const entry of bibliography) {
|
|
297
|
+
if (signal.aborted) {
|
|
298
|
+
throw signal.reason instanceof Error ? signal.reason : new Error('Aborted');
|
|
299
|
+
}
|
|
300
|
+
// Skip the network call when local identifier validation already failed.
|
|
301
|
+
const existence = identifierInvalid?.has(entry.citekey) === true
|
|
302
|
+
? identifierInvalidLayer()
|
|
303
|
+
: await processEntry(entry, clients, signal);
|
|
304
|
+
entries.push({ citekey: entry.citekey, existence });
|
|
305
|
+
}
|
|
306
|
+
return { entries };
|
|
307
|
+
}
|
|
308
|
+
//# sourceMappingURL=existence.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"existence.js","sourceRoot":"","sources":["../src/existence.ts"],"names":[],"mappings":"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"}
|
package/dist/http.d.ts
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTP utility for URL verification.
|
|
3
|
+
*
|
|
4
|
+
* Provides an HttpClient backed by the native Node 20 fetch API with per-origin
|
|
5
|
+
* concurrency queuing, jittered-backoff retry on 5xx/network errors, manual
|
|
6
|
+
* redirect following with SSRF mitigation, and a higher-level headCheck utility
|
|
7
|
+
* for canonical-edition URL verification.
|
|
8
|
+
*
|
|
9
|
+
* Note: The spec calls for undici.request directly, but we use the native Node
|
|
10
|
+
* global fetch (which is backed by undici internally) because undici is not
|
|
11
|
+
* separately installed in this project. We use `redirect: 'manual'` to achieve
|
|
12
|
+
* the same manual redirect-following behaviour.
|
|
13
|
+
*/
|
|
14
|
+
import type { Cache } from './cache/fs-cache.js';
|
|
15
|
+
export interface HttpResponse {
|
|
16
|
+
status: number;
|
|
17
|
+
headers: Record<string, string>;
|
|
18
|
+
body: unknown;
|
|
19
|
+
}
|
|
20
|
+
export interface HttpRequestOptions {
|
|
21
|
+
headers?: Record<string, string>;
|
|
22
|
+
signal?: AbortSignal;
|
|
23
|
+
timeoutMs?: number;
|
|
24
|
+
}
|
|
25
|
+
export interface HttpHeadOptions extends HttpRequestOptions {
|
|
26
|
+
followRedirects?: boolean;
|
|
27
|
+
maxRedirects?: number;
|
|
28
|
+
}
|
|
29
|
+
export interface HttpHeadResponse {
|
|
30
|
+
status: number;
|
|
31
|
+
finalUrl: string;
|
|
32
|
+
redirectChain: string[];
|
|
33
|
+
}
|
|
34
|
+
export interface HttpClient {
|
|
35
|
+
get(url: string, opts?: HttpRequestOptions): Promise<HttpResponse>;
|
|
36
|
+
head(url: string, opts?: HttpHeadOptions): Promise<HttpHeadResponse>;
|
|
37
|
+
}
|
|
38
|
+
export interface CreateHttpClientOptions {
|
|
39
|
+
userAgent?: string;
|
|
40
|
+
defaultTimeoutMs?: number;
|
|
41
|
+
maxRetries?: number;
|
|
42
|
+
retryBaseMs?: number;
|
|
43
|
+
perOriginConcurrency?: number;
|
|
44
|
+
totalDeadlineMs?: number;
|
|
45
|
+
/**
|
|
46
|
+
* Test-only escape hatch. When true, the per-hop private-IP SSRF guard is
|
|
47
|
+
* skipped so the in-process loopback test server (127.0.0.1) is reachable.
|
|
48
|
+
* Production callers MUST leave this unset/false — the secure default rejects
|
|
49
|
+
* private addresses on every hop.
|
|
50
|
+
*/
|
|
51
|
+
allowPrivateHosts?: boolean;
|
|
52
|
+
}
|
|
53
|
+
export declare class HttpError extends Error {
|
|
54
|
+
name: "HttpError";
|
|
55
|
+
readonly status: number | undefined;
|
|
56
|
+
readonly cause: unknown;
|
|
57
|
+
constructor(message: string, status?: number, cause?: unknown);
|
|
58
|
+
}
|
|
59
|
+
export declare function isPrivateIp(ip: string): boolean;
|
|
60
|
+
/**
|
|
61
|
+
* Decide whether a configured API base URL targets a private / loopback host.
|
|
62
|
+
*
|
|
63
|
+
* The per-hop SSRF guard exists to stop *untrusted bibliography URLs* reaching
|
|
64
|
+
* internal addresses. The `[apis] *_base` endpoints, by contrast, are
|
|
65
|
+
* operator-controlled configuration; pointing them at `http://127.0.0.1:PORT`
|
|
66
|
+
* (a local stub or dev mirror) is a legitimate, explicit choice. Callers use
|
|
67
|
+
* this to opt a configured-endpoint client into `allowPrivateHosts` so the
|
|
68
|
+
* operator's deliberate localhost config is honored without weakening the guard
|
|
69
|
+
* on bibliography-derived URLs. Returns false for any unparseable / public base.
|
|
70
|
+
*/
|
|
71
|
+
export declare function isPrivateApiBase(baseUrl: string | null | undefined): boolean;
|
|
72
|
+
export declare function isHostAllowed(host: string, whitelist: string[]): boolean;
|
|
73
|
+
/**
|
|
74
|
+
* Parse a Retry-After header value into a delay in milliseconds, or null if it
|
|
75
|
+
* is absent / unparseable. Accepts delta-seconds ("120") and an HTTP-date.
|
|
76
|
+
* `now` is injectable for deterministic tests.
|
|
77
|
+
*/
|
|
78
|
+
export declare function parseRetryAfterMs(value: string | undefined, now?: number): number | null;
|
|
79
|
+
export declare function createHttpClient(opts?: CreateHttpClientOptions): HttpClient;
|
|
80
|
+
export interface HeadCheckOptions {
|
|
81
|
+
http: HttpClient;
|
|
82
|
+
cache?: Cache;
|
|
83
|
+
trustedHosts: string[];
|
|
84
|
+
}
|
|
85
|
+
export type HeadCheckResult = {
|
|
86
|
+
ok: true;
|
|
87
|
+
status: number;
|
|
88
|
+
finalUrl: string;
|
|
89
|
+
redirectChain: string[];
|
|
90
|
+
host: string;
|
|
91
|
+
} | {
|
|
92
|
+
ok: false;
|
|
93
|
+
reason: 'dead-url' | 'wrong-host' | 'too-many-redirects' | 'timeout' | 'network-error';
|
|
94
|
+
details: string;
|
|
95
|
+
};
|
|
96
|
+
export declare function headCheck(url: string, opts: HeadCheckOptions, signal: AbortSignal): Promise<HeadCheckResult>;
|
|
97
|
+
//# sourceMappingURL=http.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"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"}
|