@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.
Files changed (127) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +147 -0
  3. package/dist/cache/fs-cache.d.ts +55 -0
  4. package/dist/cache/fs-cache.d.ts.map +1 -0
  5. package/dist/cache/fs-cache.js +264 -0
  6. package/dist/cache/fs-cache.js.map +1 -0
  7. package/dist/canonical.d.ts +29 -0
  8. package/dist/canonical.d.ts.map +1 -0
  9. package/dist/canonical.js +132 -0
  10. package/dist/canonical.js.map +1 -0
  11. package/dist/check.d.ts +140 -0
  12. package/dist/check.d.ts.map +1 -0
  13. package/dist/check.js +646 -0
  14. package/dist/check.js.map +1 -0
  15. package/dist/cli.d.ts +19 -0
  16. package/dist/cli.d.ts.map +1 -0
  17. package/dist/cli.js +357 -0
  18. package/dist/cli.js.map +1 -0
  19. package/dist/config.d.ts +175 -0
  20. package/dist/config.d.ts.map +1 -0
  21. package/dist/config.js +180 -0
  22. package/dist/config.js.map +1 -0
  23. package/dist/databases/crossref.d.ts +53 -0
  24. package/dist/databases/crossref.d.ts.map +1 -0
  25. package/dist/databases/crossref.js +138 -0
  26. package/dist/databases/crossref.js.map +1 -0
  27. package/dist/databases/index.d.ts +12 -0
  28. package/dist/databases/index.d.ts.map +1 -0
  29. package/dist/databases/index.js +9 -0
  30. package/dist/databases/index.js.map +1 -0
  31. package/dist/databases/openalex.d.ts +29 -0
  32. package/dist/databases/openalex.d.ts.map +1 -0
  33. package/dist/databases/openalex.js +117 -0
  34. package/dist/databases/openalex.js.map +1 -0
  35. package/dist/databases/openlibrary.d.ts +26 -0
  36. package/dist/databases/openlibrary.d.ts.map +1 -0
  37. package/dist/databases/openlibrary.js +79 -0
  38. package/dist/databases/openlibrary.js.map +1 -0
  39. package/dist/databases/worldcat.d.ts +33 -0
  40. package/dist/databases/worldcat.d.ts.map +1 -0
  41. package/dist/databases/worldcat.js +145 -0
  42. package/dist/databases/worldcat.js.map +1 -0
  43. package/dist/doctor.d.ts +44 -0
  44. package/dist/doctor.d.ts.map +1 -0
  45. package/dist/doctor.js +386 -0
  46. package/dist/doctor.js.map +1 -0
  47. package/dist/existence.d.ts +70 -0
  48. package/dist/existence.d.ts.map +1 -0
  49. package/dist/existence.js +308 -0
  50. package/dist/existence.js.map +1 -0
  51. package/dist/http.d.ts +97 -0
  52. package/dist/http.d.ts.map +1 -0
  53. package/dist/http.js +543 -0
  54. package/dist/http.js.map +1 -0
  55. package/dist/identifiers.d.ts +44 -0
  56. package/dist/identifiers.d.ts.map +1 -0
  57. package/dist/identifiers.js +111 -0
  58. package/dist/identifiers.js.map +1 -0
  59. package/dist/index.d.ts +9 -0
  60. package/dist/index.d.ts.map +1 -0
  61. package/dist/index.js +8 -0
  62. package/dist/index.js.map +1 -0
  63. package/dist/linkage.d.ts +29 -0
  64. package/dist/linkage.d.ts.map +1 -0
  65. package/dist/linkage.js +73 -0
  66. package/dist/linkage.js.map +1 -0
  67. package/dist/markdown/blocks.d.ts +19 -0
  68. package/dist/markdown/blocks.d.ts.map +1 -0
  69. package/dist/markdown/blocks.js +69 -0
  70. package/dist/markdown/blocks.js.map +1 -0
  71. package/dist/markdown/citekeys.d.ts +22 -0
  72. package/dist/markdown/citekeys.d.ts.map +1 -0
  73. package/dist/markdown/citekeys.js +100 -0
  74. package/dist/markdown/citekeys.js.map +1 -0
  75. package/dist/markdown/glob.d.ts +18 -0
  76. package/dist/markdown/glob.d.ts.map +1 -0
  77. package/dist/markdown/glob.js +26 -0
  78. package/dist/markdown/glob.js.map +1 -0
  79. package/dist/markdown/prose.d.ts +19 -0
  80. package/dist/markdown/prose.d.ts.map +1 -0
  81. package/dist/markdown/prose.js +81 -0
  82. package/dist/markdown/prose.js.map +1 -0
  83. package/dist/output/json.d.ts +21 -0
  84. package/dist/output/json.d.ts.map +1 -0
  85. package/dist/output/json.js +24 -0
  86. package/dist/output/json.js.map +1 -0
  87. package/dist/output/markdown.d.ts +21 -0
  88. package/dist/output/markdown.d.ts.map +1 -0
  89. package/dist/output/markdown.js +194 -0
  90. package/dist/output/markdown.js.map +1 -0
  91. package/dist/output/sarif.d.ts +31 -0
  92. package/dist/output/sarif.d.ts.map +1 -0
  93. package/dist/output/sarif.js +322 -0
  94. package/dist/output/sarif.js.map +1 -0
  95. package/dist/output/text.d.ts +27 -0
  96. package/dist/output/text.d.ts.map +1 -0
  97. package/dist/output/text.js +212 -0
  98. package/dist/output/text.js.map +1 -0
  99. package/dist/phrases/load.d.ts +34 -0
  100. package/dist/phrases/load.d.ts.map +1 -0
  101. package/dist/phrases/load.js +148 -0
  102. package/dist/phrases/load.js.map +1 -0
  103. package/dist/phrases.d.ts +27 -0
  104. package/dist/phrases.d.ts.map +1 -0
  105. package/dist/phrases.js +116 -0
  106. package/dist/phrases.js.map +1 -0
  107. package/dist/schema/csl.d.ts +429 -0
  108. package/dist/schema/csl.d.ts.map +1 -0
  109. package/dist/schema/csl.js +101 -0
  110. package/dist/schema/csl.js.map +1 -0
  111. package/dist/schema/output.d.ts +1116 -0
  112. package/dist/schema/output.d.ts.map +1 -0
  113. package/dist/schema/output.js +419 -0
  114. package/dist/schema/output.js.map +1 -0
  115. package/dist/suppression.d.ts +106 -0
  116. package/dist/suppression.d.ts.map +1 -0
  117. package/dist/suppression.js +134 -0
  118. package/dist/suppression.js.map +1 -0
  119. package/dist/version.d.ts +11 -0
  120. package/dist/version.d.ts.map +1 -0
  121. package/dist/version.js +14 -0
  122. package/dist/version.js.map +1 -0
  123. package/dist/worklist.d.ts +32 -0
  124. package/dist/worklist.d.ts.map +1 -0
  125. package/dist/worklist.js +211 -0
  126. package/dist/worklist.js.map +1 -0
  127. 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":"AAAA;;;;;;;;;;;;GAYG;AAKH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AAMjD,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,IAAI,EAAE,OAAO,CAAC;CACf;AAED,MAAM,WAAW,kBAAkB;IACjC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,eAAgB,SAAQ,kBAAkB;IACzD,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,EAAE,CAAC;CACzB;AAED,MAAM,WAAW,UAAU;IACzB,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,kBAAkB,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC;IACnE,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAAC;CACtE;AAED,MAAM,WAAW,uBAAuB;IACtC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB;;;;;OAKG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAC;CAC7B;AAMD,qBAAa,SAAU,SAAQ,KAAK;IACzB,IAAI,EAAG,WAAW,CAAU;IACrC,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;IACpC,SAAkB,KAAK,EAAE,OAAO,CAAC;gBAErB,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO;CAQ9D;AA2CD,wBAAgB,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAmC/C;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,CAW5E;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,OAAO,CAQxE;AAyDD;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,EAAE,GAAG,GAAE,MAAmB,GAAG,MAAM,GAAG,IAAI,CAUpG;AAkGD,wBAAgB,gBAAgB,CAAC,IAAI,CAAC,EAAE,uBAAuB,GAAG,UAAU,CA0N3E;AAMD,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,UAAU,CAAC;IACjB,KAAK,CAAC,EAAE,KAAK,CAAC;IACd,YAAY,EAAE,MAAM,EAAE,CAAC;CACxB;AAED,MAAM,MAAM,eAAe,GACvB;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,aAAa,EAAE,MAAM,EAAE,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GACrF;IACE,EAAE,EAAE,KAAK,CAAC;IACV,MAAM,EAAE,UAAU,GAAG,YAAY,GAAG,oBAAoB,GAAG,SAAS,GAAG,eAAe,CAAC;IACvF,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AAEN,wBAAsB,SAAS,CAC7B,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,gBAAgB,EACtB,MAAM,EAAE,WAAW,GAClB,OAAO,CAAC,eAAe,CAAC,CAsE1B"}