@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
package/dist/config.js
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* bibcheck configuration schema and loader.
|
|
3
|
+
*
|
|
4
|
+
* Reads `bibcheck.toml` from the project root, validates it against the
|
|
5
|
+
* Zod schema, and returns a frozen typed Config. Returns defaults when no
|
|
6
|
+
* config file is present.
|
|
7
|
+
*/
|
|
8
|
+
import { z } from 'zod';
|
|
9
|
+
import { parse as parseToml } from 'smol-toml';
|
|
10
|
+
import { readFile, access } from 'node:fs/promises';
|
|
11
|
+
import { join } from 'node:path';
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
// Schema sections
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
const BibliographySchema = z.object({
|
|
16
|
+
file: z.string().default('docs/sources.json'),
|
|
17
|
+
});
|
|
18
|
+
const DocsSchema = z.object({
|
|
19
|
+
include: z.array(z.string()).default(['docs/**/*.md']),
|
|
20
|
+
exclude: z.array(z.string()).default([]),
|
|
21
|
+
});
|
|
22
|
+
const TrustedHostsSchema = z.object({
|
|
23
|
+
hosts: z.array(z.string()).default([
|
|
24
|
+
'hathitrust.org',
|
|
25
|
+
'archive.org',
|
|
26
|
+
'oll.libertyfund.org',
|
|
27
|
+
'plato.stanford.edu',
|
|
28
|
+
'philpapers.org',
|
|
29
|
+
'loc.gov',
|
|
30
|
+
'dnb.de',
|
|
31
|
+
'bnf.fr',
|
|
32
|
+
]),
|
|
33
|
+
});
|
|
34
|
+
const PhrasesSchema = z.object({
|
|
35
|
+
file: z.string().nullable().default(null),
|
|
36
|
+
});
|
|
37
|
+
const SourceTypeEntrySchema = z.object({
|
|
38
|
+
warn_load_bearing: z.boolean().optional(),
|
|
39
|
+
allow_load_bearing: z.boolean().optional(),
|
|
40
|
+
/**
|
|
41
|
+
* T23 — source-type gating rule. When `false`, a `not-found-in-databases`
|
|
42
|
+
* (unverifiable-absence) result for an entry of this CSL type does NOT gate
|
|
43
|
+
* `bibcheck check` — e.g. a pre-DOI manuscript / archival source for which no
|
|
44
|
+
* DOI was ever expected. The finding is still reported (informational), never
|
|
45
|
+
* dropped. Omitted / `true` ⇒ the Q1 secure default (gate). Governs only the
|
|
46
|
+
* not-found gate; malformed identifiers, canonical issues, and metadata
|
|
47
|
+
* mismatches always gate (use a per-entry `bibcheck-allow` note for those).
|
|
48
|
+
*/
|
|
49
|
+
gate_not_found: z.boolean().optional(),
|
|
50
|
+
});
|
|
51
|
+
const ApisSchema = z.object({
|
|
52
|
+
crossref_mailto: z.string().nullable().default(null),
|
|
53
|
+
openalex_mailto: z.string().nullable().default(null),
|
|
54
|
+
// Base URLs for each bibliographic database. When omitted, each DB client
|
|
55
|
+
// and the doctor connectivity check fall back to the real public endpoint
|
|
56
|
+
// (see API_BASE_DEFAULTS). Overridable (e.g. to a localhost stub) for
|
|
57
|
+
// hermetic testing. Validated as URLs when present; a trailing slash is
|
|
58
|
+
// tolerated (clients strip it). Kept optional so the effective default lives
|
|
59
|
+
// in one place (API_BASE_DEFAULTS) and consumers stay tolerant of an absent
|
|
60
|
+
// value.
|
|
61
|
+
crossref_base: z.string().url().optional(),
|
|
62
|
+
openalex_base: z.string().url().optional(),
|
|
63
|
+
openlibrary_base: z.string().url().optional(),
|
|
64
|
+
});
|
|
65
|
+
/**
|
|
66
|
+
* Effective default base URLs for the bibliographic database APIs. These are
|
|
67
|
+
* the real public endpoints used whenever the corresponding `[apis] *_base`
|
|
68
|
+
* config field is omitted. (WorldCat / OCLC Classify was removed in 0.2.0.)
|
|
69
|
+
*/
|
|
70
|
+
export const API_BASE_DEFAULTS = {
|
|
71
|
+
crossref: 'https://api.crossref.org',
|
|
72
|
+
openalex: 'https://api.openalex.org',
|
|
73
|
+
openlibrary: 'https://openlibrary.org',
|
|
74
|
+
};
|
|
75
|
+
const CacheSchema = z.object({
|
|
76
|
+
dir: z.string().default('.bibcheck-cache'),
|
|
77
|
+
max_size_mb: z.number().nullable().default(256),
|
|
78
|
+
});
|
|
79
|
+
// ---------------------------------------------------------------------------
|
|
80
|
+
// Top-level schema
|
|
81
|
+
// ---------------------------------------------------------------------------
|
|
82
|
+
export const ConfigSchema = z.object({
|
|
83
|
+
bibliography: BibliographySchema.default({}),
|
|
84
|
+
docs: DocsSchema.default({}),
|
|
85
|
+
trusted_hosts: TrustedHostsSchema.default({}),
|
|
86
|
+
phrases: PhrasesSchema.default({}),
|
|
87
|
+
source_types: z.record(z.string(), SourceTypeEntrySchema).default({}),
|
|
88
|
+
edition_discipline: z.record(z.string(), z.string()).default({}),
|
|
89
|
+
apis: ApisSchema.default({}),
|
|
90
|
+
cache: CacheSchema.default({}),
|
|
91
|
+
});
|
|
92
|
+
// ---------------------------------------------------------------------------
|
|
93
|
+
// Error class
|
|
94
|
+
// ---------------------------------------------------------------------------
|
|
95
|
+
export class ConfigError extends Error {
|
|
96
|
+
constructor(message) {
|
|
97
|
+
super(message);
|
|
98
|
+
this.name = 'ConfigError';
|
|
99
|
+
if (Error.captureStackTrace) {
|
|
100
|
+
Error.captureStackTrace(this, ConfigError);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
// ---------------------------------------------------------------------------
|
|
105
|
+
// Prototype-pollution guard
|
|
106
|
+
// ---------------------------------------------------------------------------
|
|
107
|
+
const DANGEROUS_KEYS = new Set(['__proto__', 'constructor', 'prototype']);
|
|
108
|
+
function checkPollution(node, path) {
|
|
109
|
+
if (node === null || typeof node !== 'object')
|
|
110
|
+
return;
|
|
111
|
+
for (const key of Object.keys(node)) {
|
|
112
|
+
const childPath = path ? `${path}.${key}` : key;
|
|
113
|
+
if (DANGEROUS_KEYS.has(key)) {
|
|
114
|
+
throw new ConfigError(`Prototype pollution attempt: ${childPath}`);
|
|
115
|
+
}
|
|
116
|
+
checkPollution(node[key], childPath);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
// ---------------------------------------------------------------------------
|
|
120
|
+
// Deep freeze helper
|
|
121
|
+
// ---------------------------------------------------------------------------
|
|
122
|
+
function deepFreeze(value) {
|
|
123
|
+
if (value === null || typeof value !== 'object')
|
|
124
|
+
return value;
|
|
125
|
+
Object.freeze(value);
|
|
126
|
+
for (const v of Object.values(value)) {
|
|
127
|
+
deepFreeze(v);
|
|
128
|
+
}
|
|
129
|
+
return value;
|
|
130
|
+
}
|
|
131
|
+
export async function loadConfig(opts) {
|
|
132
|
+
const cwd = opts?.cwd ?? process.cwd();
|
|
133
|
+
const configPath = opts?.path ? join(cwd, opts.path) : join(cwd, 'bibcheck.toml');
|
|
134
|
+
let raw;
|
|
135
|
+
try {
|
|
136
|
+
await access(configPath);
|
|
137
|
+
}
|
|
138
|
+
catch {
|
|
139
|
+
// File does not exist — return defaults
|
|
140
|
+
return deepFreeze(ConfigSchema.parse({}));
|
|
141
|
+
}
|
|
142
|
+
let contents;
|
|
143
|
+
try {
|
|
144
|
+
contents = await readFile(configPath, 'utf-8');
|
|
145
|
+
}
|
|
146
|
+
catch (err) {
|
|
147
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
148
|
+
throw new ConfigError(`Failed to read config file: ${message}`);
|
|
149
|
+
}
|
|
150
|
+
// Empty file is valid — treat as all-defaults
|
|
151
|
+
if (contents.trim() === '') {
|
|
152
|
+
return deepFreeze(ConfigSchema.parse({}));
|
|
153
|
+
}
|
|
154
|
+
try {
|
|
155
|
+
raw = parseToml(contents);
|
|
156
|
+
}
|
|
157
|
+
catch (err) {
|
|
158
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
159
|
+
throw new ConfigError(`TOML parse error in ${configPath}: ${message}`);
|
|
160
|
+
}
|
|
161
|
+
// Prototype-pollution guard
|
|
162
|
+
checkPollution(raw, '');
|
|
163
|
+
try {
|
|
164
|
+
return deepFreeze(ConfigSchema.parse(raw));
|
|
165
|
+
}
|
|
166
|
+
catch (err) {
|
|
167
|
+
if (err instanceof z.ZodError) {
|
|
168
|
+
const first = err.issues[0];
|
|
169
|
+
if (first !== undefined) {
|
|
170
|
+
const fieldPath = first.path.join('.');
|
|
171
|
+
throw new ConfigError(`${fieldPath}: ${first.message}`);
|
|
172
|
+
}
|
|
173
|
+
/* c8 ignore next */
|
|
174
|
+
// unreachable: ZodError always has at least one issue
|
|
175
|
+
throw new ConfigError(`Configuration validation failed: ${err.message}`);
|
|
176
|
+
}
|
|
177
|
+
throw err;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CrossRef DOI lookup client.
|
|
3
|
+
*
|
|
4
|
+
* Queries https://api.crossref.org/works/<doi> and returns normalised
|
|
5
|
+
* DatabaseLookupResult metadata. Supports the CrossRef polite pool via a
|
|
6
|
+
* ?mailto= query param. Results are cached (default 30-day TTL).
|
|
7
|
+
*
|
|
8
|
+
* The polite-pool email is stripped from any URL fields in the raw response
|
|
9
|
+
* before caching to prevent credentials leaking into evidence output.
|
|
10
|
+
*/
|
|
11
|
+
import type { HttpClient } from '../http.js';
|
|
12
|
+
import type { Cache } from '../cache/fs-cache.js';
|
|
13
|
+
export interface DatabaseLookupResult {
|
|
14
|
+
found: boolean;
|
|
15
|
+
metadata: {
|
|
16
|
+
title?: string;
|
|
17
|
+
authors?: string[];
|
|
18
|
+
issued?: number;
|
|
19
|
+
publisher?: string;
|
|
20
|
+
doi?: string;
|
|
21
|
+
isbn?: string;
|
|
22
|
+
url?: string;
|
|
23
|
+
} | null;
|
|
24
|
+
raw: unknown;
|
|
25
|
+
}
|
|
26
|
+
export interface DatabaseClient {
|
|
27
|
+
readonly name: string;
|
|
28
|
+
}
|
|
29
|
+
export interface CrossRefClientOptions {
|
|
30
|
+
http: HttpClient;
|
|
31
|
+
cache: Cache;
|
|
32
|
+
mailto?: string | null;
|
|
33
|
+
/** Base URL for the CrossRef API. Defaults to the public endpoint. */
|
|
34
|
+
baseUrl?: string;
|
|
35
|
+
}
|
|
36
|
+
/** Remove a single trailing slash so path joins are unambiguous. */
|
|
37
|
+
export declare function trimTrailingSlash(url: string): string;
|
|
38
|
+
export interface CrossRefClient extends DatabaseClient {
|
|
39
|
+
readonly name: 'crossref';
|
|
40
|
+
lookupByDoi(doi: string, signal?: AbortSignal): Promise<DatabaseLookupResult>;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Strip the ?mailto= query parameter from a URL string.
|
|
44
|
+
* Returns the original string unchanged if it is not a valid URL.
|
|
45
|
+
*/
|
|
46
|
+
export declare function stripMailto(url: string): string;
|
|
47
|
+
/**
|
|
48
|
+
* Recursively walk an unknown value and strip ?mailto= from any string
|
|
49
|
+
* fields that look like URLs. Returns a new value; does not mutate.
|
|
50
|
+
*/
|
|
51
|
+
export declare function sanitizeMailto(value: unknown): unknown;
|
|
52
|
+
export declare function createCrossRefClient(opts: CrossRefClientOptions): CrossRefClient;
|
|
53
|
+
//# sourceMappingURL=crossref.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"crossref.d.ts","sourceRoot":"","sources":["../../src/databases/crossref.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAgB,MAAM,YAAY,CAAC;AAC3D,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,sBAAsB,CAAC;AAOlD,MAAM,WAAW,oBAAoB;IACnC,KAAK,EAAE,OAAO,CAAC;IACf,QAAQ,EAAE;QACR,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;QACnB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,GAAG,CAAC,EAAE,MAAM,CAAC;QACb,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,GAAG,CAAC,EAAE,MAAM,CAAC;KACd,GAAG,IAAI,CAAC;IACT,GAAG,EAAE,OAAO,CAAC;CACd;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAMD,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,UAAU,CAAC;IACjB,KAAK,EAAE,KAAK,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,sEAAsE;IACtE,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAID,oEAAoE;AACpE,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAErD;AAED,MAAM,WAAW,cAAe,SAAQ,cAAc;IACpD,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAC1B,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAAC;CAC/E;AAmCD;;;GAGG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAY/C;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAmBtD;AAoCD,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,qBAAqB,GAAG,cAAc,CA0DhF"}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CrossRef DOI lookup client.
|
|
3
|
+
*
|
|
4
|
+
* Queries https://api.crossref.org/works/<doi> and returns normalised
|
|
5
|
+
* DatabaseLookupResult metadata. Supports the CrossRef polite pool via a
|
|
6
|
+
* ?mailto= query param. Results are cached (default 30-day TTL).
|
|
7
|
+
*
|
|
8
|
+
* The polite-pool email is stripped from any URL fields in the raw response
|
|
9
|
+
* before caching to prevent credentials leaking into evidence output.
|
|
10
|
+
*/
|
|
11
|
+
const DEFAULT_CROSSREF_BASE = 'https://api.crossref.org';
|
|
12
|
+
/** Remove a single trailing slash so path joins are unambiguous. */
|
|
13
|
+
export function trimTrailingSlash(url) {
|
|
14
|
+
return url.endsWith('/') ? url.slice(0, -1) : url;
|
|
15
|
+
}
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
// Helpers
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
/**
|
|
20
|
+
* Strip the ?mailto= query parameter from a URL string.
|
|
21
|
+
* Returns the original string unchanged if it is not a valid URL.
|
|
22
|
+
*/
|
|
23
|
+
export function stripMailto(url) {
|
|
24
|
+
let parsed;
|
|
25
|
+
try {
|
|
26
|
+
parsed = new URL(url);
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return url;
|
|
30
|
+
}
|
|
31
|
+
if (parsed.searchParams.has('mailto')) {
|
|
32
|
+
parsed.searchParams.delete('mailto');
|
|
33
|
+
return parsed.toString();
|
|
34
|
+
}
|
|
35
|
+
return url;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Recursively walk an unknown value and strip ?mailto= from any string
|
|
39
|
+
* fields that look like URLs. Returns a new value; does not mutate.
|
|
40
|
+
*/
|
|
41
|
+
export function sanitizeMailto(value) {
|
|
42
|
+
if (typeof value === 'string') {
|
|
43
|
+
// Only attempt stripMailto for strings that look like URLs.
|
|
44
|
+
if (value.startsWith('http://') || value.startsWith('https://')) {
|
|
45
|
+
return stripMailto(value);
|
|
46
|
+
}
|
|
47
|
+
return value;
|
|
48
|
+
}
|
|
49
|
+
if (Array.isArray(value)) {
|
|
50
|
+
return value.map(sanitizeMailto);
|
|
51
|
+
}
|
|
52
|
+
if (typeof value === 'object' && value !== null) {
|
|
53
|
+
const result = {};
|
|
54
|
+
for (const [k, v] of Object.entries(value)) {
|
|
55
|
+
result[k] = sanitizeMailto(v);
|
|
56
|
+
}
|
|
57
|
+
return result;
|
|
58
|
+
}
|
|
59
|
+
return value;
|
|
60
|
+
}
|
|
61
|
+
function isCrossRefSuccess(body) {
|
|
62
|
+
if (typeof body !== 'object' || body === null)
|
|
63
|
+
return false;
|
|
64
|
+
const b = body;
|
|
65
|
+
return b['status'] === 'ok' && typeof b['message'] === 'object' && b['message'] !== null;
|
|
66
|
+
}
|
|
67
|
+
function mapMetadata(msg) {
|
|
68
|
+
const authors = msg.author
|
|
69
|
+
?.map((a) => [a.family, a.given].filter(Boolean).join(', '))
|
|
70
|
+
.filter((s) => s.length > 0);
|
|
71
|
+
// noUncheckedIndexedAccess: date-parts[0] could be undefined on malformed data.
|
|
72
|
+
const dateParts = msg.issued?.['date-parts'];
|
|
73
|
+
const firstPart = dateParts?.[0];
|
|
74
|
+
const year = firstPart?.[0];
|
|
75
|
+
const isbn = msg.ISBN?.[0];
|
|
76
|
+
const title = msg.title?.[0];
|
|
77
|
+
return {
|
|
78
|
+
title,
|
|
79
|
+
authors: authors && authors.length > 0 ? authors : undefined,
|
|
80
|
+
issued: typeof year === 'number' ? year : undefined,
|
|
81
|
+
publisher: msg.publisher,
|
|
82
|
+
doi: msg.DOI,
|
|
83
|
+
isbn,
|
|
84
|
+
url: msg.URL,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
// ---------------------------------------------------------------------------
|
|
88
|
+
// Factory
|
|
89
|
+
// ---------------------------------------------------------------------------
|
|
90
|
+
export function createCrossRefClient(opts) {
|
|
91
|
+
const { http, cache, mailto } = opts;
|
|
92
|
+
const base = trimTrailingSlash(opts.baseUrl ?? DEFAULT_CROSSREF_BASE);
|
|
93
|
+
async function lookupByDoi(doi, signal) {
|
|
94
|
+
const cacheKey = `crossref:lookupByDoi:${doi.toLowerCase()}`;
|
|
95
|
+
const cached = await cache.get(cacheKey, signal);
|
|
96
|
+
if (cached !== null) {
|
|
97
|
+
return cached;
|
|
98
|
+
}
|
|
99
|
+
const encoded = encodeURIComponent(doi);
|
|
100
|
+
let url = `${base}/works/${encoded}`;
|
|
101
|
+
if (mailto) {
|
|
102
|
+
url += `?mailto=${encodeURIComponent(mailto)}`;
|
|
103
|
+
}
|
|
104
|
+
let response;
|
|
105
|
+
try {
|
|
106
|
+
response = await http.get(url, { signal });
|
|
107
|
+
}
|
|
108
|
+
catch (err) {
|
|
109
|
+
// Network errors / 5xx after retries — propagate.
|
|
110
|
+
throw err;
|
|
111
|
+
}
|
|
112
|
+
if (response.status === 404) {
|
|
113
|
+
const result = {
|
|
114
|
+
found: false,
|
|
115
|
+
metadata: null,
|
|
116
|
+
raw: sanitizeMailto(response.body),
|
|
117
|
+
};
|
|
118
|
+
return result;
|
|
119
|
+
}
|
|
120
|
+
if (!isCrossRefSuccess(response.body)) {
|
|
121
|
+
throw new Error(`CrossRef: unexpected response body for DOI "${doi}" (status ${response.status})`);
|
|
122
|
+
}
|
|
123
|
+
const sanitizedRaw = sanitizeMailto(response.body);
|
|
124
|
+
const metadata = mapMetadata(response.body.message);
|
|
125
|
+
const result = {
|
|
126
|
+
found: true,
|
|
127
|
+
metadata,
|
|
128
|
+
raw: sanitizedRaw,
|
|
129
|
+
};
|
|
130
|
+
await cache.set(cacheKey, result);
|
|
131
|
+
return result;
|
|
132
|
+
}
|
|
133
|
+
return {
|
|
134
|
+
name: 'crossref',
|
|
135
|
+
lookupByDoi,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
//# sourceMappingURL=crossref.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"crossref.js","sourceRoot":"","sources":["../../src/databases/crossref.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Barrel re-export for the bibliographic database clients (CrossRef,
|
|
3
|
+
* OpenAlex, OpenLibrary). WorldCat / OCLC Classify was removed in 0.2.0
|
|
4
|
+
* (decommissioned endpoint; see tmp/design-review/worldcat.md).
|
|
5
|
+
*/
|
|
6
|
+
export { createCrossRefClient, stripMailto, sanitizeMailto, } from './crossref.js';
|
|
7
|
+
export type { DatabaseLookupResult, DatabaseClient, CrossRefClientOptions, CrossRefClient, } from './crossref.js';
|
|
8
|
+
export { createOpenAlexClient } from './openalex.js';
|
|
9
|
+
export type { OpenAlexClientOptions, OpenAlexClient } from './openalex.js';
|
|
10
|
+
export { createOpenLibraryClient } from './openlibrary.js';
|
|
11
|
+
export type { OpenLibraryClientOptions, OpenLibraryClient } from './openlibrary.js';
|
|
12
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/databases/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EACL,oBAAoB,EACpB,WAAW,EACX,cAAc,GACf,MAAM,eAAe,CAAC;AACvB,YAAY,EACV,oBAAoB,EACpB,cAAc,EACd,qBAAqB,EACrB,cAAc,GACf,MAAM,eAAe,CAAC;AAEvB,OAAO,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AACrD,YAAY,EAAE,qBAAqB,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAE3E,OAAO,EAAE,uBAAuB,EAAE,MAAM,kBAAkB,CAAC;AAC3D,YAAY,EAAE,wBAAwB,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Barrel re-export for the bibliographic database clients (CrossRef,
|
|
3
|
+
* OpenAlex, OpenLibrary). WorldCat / OCLC Classify was removed in 0.2.0
|
|
4
|
+
* (decommissioned endpoint; see tmp/design-review/worldcat.md).
|
|
5
|
+
*/
|
|
6
|
+
export { createCrossRefClient, stripMailto, sanitizeMailto, } from './crossref.js';
|
|
7
|
+
export { createOpenAlexClient } from './openalex.js';
|
|
8
|
+
export { createOpenLibraryClient } from './openlibrary.js';
|
|
9
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/databases/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EACL,oBAAoB,EACpB,WAAW,EACX,cAAc,GACf,MAAM,eAAe,CAAC;AAQvB,OAAO,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AAGrD,OAAO,EAAE,uBAAuB,EAAE,MAAM,kBAAkB,CAAC"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenAlex works client.
|
|
3
|
+
*
|
|
4
|
+
* Supports DOI lookup and title+author search against the OpenAlex API
|
|
5
|
+
* (https://api.openalex.org/works). Polite pool is engaged via ?mailto=.
|
|
6
|
+
* Results are cached at default TTL (30 days).
|
|
7
|
+
*
|
|
8
|
+
* Sanitization: ?mailto= is stripped from any URL strings in the raw response.
|
|
9
|
+
*/
|
|
10
|
+
import type { HttpClient } from '../http.js';
|
|
11
|
+
import type { Cache } from '../cache/fs-cache.js';
|
|
12
|
+
import type { DatabaseLookupResult, DatabaseClient } from './crossref.js';
|
|
13
|
+
import { stripMailto } from './crossref.js';
|
|
14
|
+
export type { DatabaseLookupResult, DatabaseClient };
|
|
15
|
+
export interface OpenAlexClientOptions {
|
|
16
|
+
http: HttpClient;
|
|
17
|
+
cache: Cache;
|
|
18
|
+
mailto?: string | null;
|
|
19
|
+
/** Base URL for the OpenAlex API. Defaults to the public endpoint. */
|
|
20
|
+
baseUrl?: string;
|
|
21
|
+
}
|
|
22
|
+
export interface OpenAlexClient extends DatabaseClient {
|
|
23
|
+
readonly name: 'openalex';
|
|
24
|
+
searchByTitleAuthor(title: string, authors: string[], signal?: AbortSignal): Promise<DatabaseLookupResult>;
|
|
25
|
+
lookupByDoi(doi: string, signal?: AbortSignal): Promise<DatabaseLookupResult>;
|
|
26
|
+
}
|
|
27
|
+
export { stripMailto };
|
|
28
|
+
export declare function createOpenAlexClient(opts: OpenAlexClientOptions): OpenAlexClient;
|
|
29
|
+
//# sourceMappingURL=openalex.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openalex.d.ts","sourceRoot":"","sources":["../../src/databases/openalex.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAgB,MAAM,YAAY,CAAC;AAC3D,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,sBAAsB,CAAC;AAClD,OAAO,KAAK,EAAE,oBAAoB,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAC1E,OAAO,EAAE,WAAW,EAAqC,MAAM,eAAe,CAAC;AAG/E,YAAY,EAAE,oBAAoB,EAAE,cAAc,EAAE,CAAC;AAMrD,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,UAAU,CAAC;IACjB,KAAK,EAAE,KAAK,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,sEAAsE;IACtE,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAID,MAAM,WAAW,cAAe,SAAQ,cAAc;IACpD,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAC1B,mBAAmB,CACjB,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,MAAM,EAAE,EACjB,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,oBAAoB,CAAC,CAAC;IACjC,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAAC;CAC/E;AA6BD,OAAO,EAAE,WAAW,EAAE,CAAC;AAwCvB,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,qBAAqB,GAAG,cAAc,CAuFhF"}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenAlex works client.
|
|
3
|
+
*
|
|
4
|
+
* Supports DOI lookup and title+author search against the OpenAlex API
|
|
5
|
+
* (https://api.openalex.org/works). Polite pool is engaged via ?mailto=.
|
|
6
|
+
* Results are cached at default TTL (30 days).
|
|
7
|
+
*
|
|
8
|
+
* Sanitization: ?mailto= is stripped from any URL strings in the raw response.
|
|
9
|
+
*/
|
|
10
|
+
import { stripMailto, sanitizeMailto, trimTrailingSlash } from './crossref.js';
|
|
11
|
+
const DEFAULT_OPENALEX_BASE = 'https://api.openalex.org';
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
// Helpers
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
// Re-export stripMailto for tests.
|
|
16
|
+
export { stripMailto };
|
|
17
|
+
function sanitizeRaw(value) {
|
|
18
|
+
return sanitizeMailto(value);
|
|
19
|
+
}
|
|
20
|
+
function isOpenAlexWork(body) {
|
|
21
|
+
if (typeof body !== 'object' || body === null)
|
|
22
|
+
return false;
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
function isOpenAlexSearchResponse(body) {
|
|
26
|
+
if (typeof body !== 'object' || body === null)
|
|
27
|
+
return false;
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
function mapWorkMetadata(work) {
|
|
31
|
+
const authors = work.authorships
|
|
32
|
+
?.map((a) => a.author?.display_name ?? '')
|
|
33
|
+
.filter((n) => n.length > 0);
|
|
34
|
+
return {
|
|
35
|
+
title: work.display_name,
|
|
36
|
+
authors: authors && authors.length > 0 ? authors : undefined,
|
|
37
|
+
issued: typeof work.publication_year === 'number' ? work.publication_year : undefined,
|
|
38
|
+
doi: work.doi,
|
|
39
|
+
url: work.id,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
function appendMailto(url, mailto) {
|
|
43
|
+
if (!mailto)
|
|
44
|
+
return url;
|
|
45
|
+
const separator = url.includes('?') ? '&' : '?';
|
|
46
|
+
return `${url}${separator}mailto=${encodeURIComponent(mailto)}`;
|
|
47
|
+
}
|
|
48
|
+
// ---------------------------------------------------------------------------
|
|
49
|
+
// Factory
|
|
50
|
+
// ---------------------------------------------------------------------------
|
|
51
|
+
export function createOpenAlexClient(opts) {
|
|
52
|
+
const { http, cache, mailto } = opts;
|
|
53
|
+
const base = trimTrailingSlash(opts.baseUrl ?? DEFAULT_OPENALEX_BASE);
|
|
54
|
+
async function lookupByDoi(doi, signal) {
|
|
55
|
+
const cacheKey = `openalex:lookupByDoi:${doi.toLowerCase()}`;
|
|
56
|
+
const cached = await cache.get(cacheKey, signal);
|
|
57
|
+
if (cached !== null) {
|
|
58
|
+
return cached;
|
|
59
|
+
}
|
|
60
|
+
const encoded = encodeURIComponent(doi);
|
|
61
|
+
const baseUrl = `${base}/works/doi:${encoded}`;
|
|
62
|
+
const url = appendMailto(baseUrl, mailto);
|
|
63
|
+
let response;
|
|
64
|
+
response = await http.get(url, { signal });
|
|
65
|
+
if (response.status === 404) {
|
|
66
|
+
return { found: false, metadata: null, raw: sanitizeRaw(response.body) };
|
|
67
|
+
}
|
|
68
|
+
if (!isOpenAlexWork(response.body)) {
|
|
69
|
+
throw new Error(`OpenAlex: unexpected response body for DOI "${doi}"`);
|
|
70
|
+
}
|
|
71
|
+
const sanitizedRaw = sanitizeRaw(response.body);
|
|
72
|
+
const metadata = mapWorkMetadata(response.body);
|
|
73
|
+
const result = { found: true, metadata, raw: sanitizedRaw };
|
|
74
|
+
await cache.set(cacheKey, result);
|
|
75
|
+
return result;
|
|
76
|
+
}
|
|
77
|
+
async function searchByTitleAuthor(title, authors, signal) {
|
|
78
|
+
const firstAuthor = authors[0] ?? '';
|
|
79
|
+
const cacheKey = `openalex:searchByTitleAuthor:${title.toLowerCase()}:${firstAuthor.toLowerCase()}`;
|
|
80
|
+
const cached = await cache.get(cacheKey, signal);
|
|
81
|
+
if (cached !== null) {
|
|
82
|
+
return cached;
|
|
83
|
+
}
|
|
84
|
+
const baseUrl = `${base}/works?search=${encodeURIComponent(title)}&filter=author.display_name.search:${encodeURIComponent(firstAuthor)}&per-page=5`;
|
|
85
|
+
const url = appendMailto(baseUrl, mailto);
|
|
86
|
+
let response;
|
|
87
|
+
response = await http.get(url, { signal });
|
|
88
|
+
if (response.status === 404) {
|
|
89
|
+
return { found: false, metadata: null, raw: sanitizeRaw(response.body) };
|
|
90
|
+
}
|
|
91
|
+
if (!isOpenAlexSearchResponse(response.body)) {
|
|
92
|
+
throw new Error(`OpenAlex: unexpected search response for title "${title}"`);
|
|
93
|
+
}
|
|
94
|
+
const results = response.body.results;
|
|
95
|
+
const firstResult = results?.[0];
|
|
96
|
+
if (!firstResult) {
|
|
97
|
+
const result = {
|
|
98
|
+
found: false,
|
|
99
|
+
metadata: null,
|
|
100
|
+
raw: sanitizeRaw(response.body),
|
|
101
|
+
};
|
|
102
|
+
await cache.set(cacheKey, result);
|
|
103
|
+
return result;
|
|
104
|
+
}
|
|
105
|
+
const sanitizedRaw = sanitizeRaw(response.body);
|
|
106
|
+
const metadata = mapWorkMetadata(firstResult);
|
|
107
|
+
const result = { found: true, metadata, raw: sanitizedRaw };
|
|
108
|
+
await cache.set(cacheKey, result);
|
|
109
|
+
return result;
|
|
110
|
+
}
|
|
111
|
+
return {
|
|
112
|
+
name: 'openalex',
|
|
113
|
+
lookupByDoi,
|
|
114
|
+
searchByTitleAuthor,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
//# sourceMappingURL=openalex.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openalex.js","sourceRoot":"","sources":["../../src/databases/openalex.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenLibrary ISBN lookup client.
|
|
3
|
+
*
|
|
4
|
+
* Queries https://openlibrary.org/api/books?bibkeys=ISBN:<isbn>&format=json&jscmd=data
|
|
5
|
+
* and returns normalised DatabaseLookupResult metadata. Results are cached at
|
|
6
|
+
* the default TTL (30 days).
|
|
7
|
+
*
|
|
8
|
+
* An empty response object {} indicates no match (found: false). The client
|
|
9
|
+
* does not use a polite-pool email (OpenLibrary has no such mechanism).
|
|
10
|
+
*/
|
|
11
|
+
import type { HttpClient } from '../http.js';
|
|
12
|
+
import type { Cache } from '../cache/fs-cache.js';
|
|
13
|
+
import type { DatabaseLookupResult, DatabaseClient } from './crossref.js';
|
|
14
|
+
export type { DatabaseLookupResult, DatabaseClient };
|
|
15
|
+
export interface OpenLibraryClientOptions {
|
|
16
|
+
http: HttpClient;
|
|
17
|
+
cache: Cache;
|
|
18
|
+
/** Base URL for the OpenLibrary API. Defaults to the public endpoint. */
|
|
19
|
+
baseUrl?: string;
|
|
20
|
+
}
|
|
21
|
+
export interface OpenLibraryClient extends DatabaseClient {
|
|
22
|
+
readonly name: 'openlibrary';
|
|
23
|
+
lookupByIsbn(isbn: string, signal?: AbortSignal): Promise<DatabaseLookupResult>;
|
|
24
|
+
}
|
|
25
|
+
export declare function createOpenLibraryClient(opts: OpenLibraryClientOptions): OpenLibraryClient;
|
|
26
|
+
//# sourceMappingURL=openlibrary.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openlibrary.d.ts","sourceRoot":"","sources":["../../src/databases/openlibrary.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAgB,MAAM,YAAY,CAAC;AAC3D,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,sBAAsB,CAAC;AAClD,OAAO,KAAK,EAAE,oBAAoB,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAG1E,YAAY,EAAE,oBAAoB,EAAE,cAAc,EAAE,CAAC;AAMrD,MAAM,WAAW,wBAAwB;IACvC,IAAI,EAAE,UAAU,CAAC;IACjB,KAAK,EAAE,KAAK,CAAC;IACb,yEAAyE;IACzE,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAID,MAAM,WAAW,iBAAkB,SAAQ,cAAc;IACvD,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC;IAC7B,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAAC;CACjF;AA8DD,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,wBAAwB,GAAG,iBAAiB,CA2CzF"}
|