@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
package/dist/doctor.js ADDED
@@ -0,0 +1,386 @@
1
+ /**
2
+ * bibcheck doctor — diagnostic runner.
3
+ *
4
+ * Runs a series of environment and configuration checks and returns a
5
+ * structured result. Each check emits a DoctorCheck entry with a name,
6
+ * status, and human-readable message. The CLI (T15) is responsible for
7
+ * rendering the output; this module does no I/O beyond what is needed
8
+ * to perform the checks.
9
+ *
10
+ * SECURITY: API keys, polite-pool email addresses, and full request URLs
11
+ * (which might contain ?mailto= query params) MUST NOT appear in any
12
+ * DoctorCheck.message or .details value.
13
+ */
14
+ import { API_BASE_DEFAULTS } from './config.js';
15
+ import { resolve, join } from 'node:path';
16
+ import { rm } from 'node:fs/promises';
17
+ import { loadBibliography, BibliographyParseError } from './schema/csl.js';
18
+ import { loadDenylist, PhraseLoaderError } from './phrases/load.js';
19
+ // ---------------------------------------------------------------------------
20
+ // Internal helpers
21
+ // ---------------------------------------------------------------------------
22
+ /** Minimum supported Node major version. */
23
+ const MIN_NODE_MAJOR = 20;
24
+ /** Remove a single trailing slash so connectivity URLs join cleanly. */
25
+ function trimTrailingSlash(url) {
26
+ return url.endsWith('/') ? url.slice(0, -1) : url;
27
+ }
28
+ /** Regex for basic hostname validation. */
29
+ const HOSTNAME_RE = /^[a-z0-9.-]+\.[a-z]{2,}$/;
30
+ /** Resolve the injectable fs interface, falling back to node:fs/promises. */
31
+ async function resolveFs(fsDep) {
32
+ if (fsDep !== undefined)
33
+ return fsDep;
34
+ const nodeFs = await import('node:fs/promises');
35
+ return {
36
+ stat: nodeFs.stat.bind(nodeFs),
37
+ access: nodeFs.access.bind(nodeFs),
38
+ readdir: (p) => nodeFs.readdir(p),
39
+ };
40
+ }
41
+ /**
42
+ * Walk a directory recursively and sum total file sizes.
43
+ * Returns 0 if the directory does not exist or is not accessible.
44
+ */
45
+ async function sumDirBytes(dirPath, fsModule) {
46
+ try {
47
+ await fsModule.access(dirPath);
48
+ }
49
+ catch {
50
+ return 0;
51
+ }
52
+ let total = 0;
53
+ const stack = [dirPath];
54
+ while (stack.length > 0) {
55
+ const current = stack.pop();
56
+ let entries;
57
+ try {
58
+ entries = await fsModule.readdir(current);
59
+ }
60
+ catch {
61
+ continue;
62
+ }
63
+ for (const entry of entries) {
64
+ const entryPath = join(current, entry);
65
+ try {
66
+ const s = await fsModule.stat(entryPath);
67
+ if (s.isDirectory()) {
68
+ stack.push(entryPath);
69
+ }
70
+ else {
71
+ total += s.size;
72
+ }
73
+ }
74
+ catch {
75
+ // ignore unreadable entries
76
+ }
77
+ }
78
+ }
79
+ return total;
80
+ }
81
+ /** Perform a connectivity check against an API endpoint. */
82
+ async function checkApiConnectivity(name, url, http, signal) {
83
+ if (signal.aborted) {
84
+ throw signal.reason;
85
+ }
86
+ try {
87
+ const response = await http.head(url, { signal, timeoutMs: 5000, followRedirects: false });
88
+ const status = response.status;
89
+ // Any response (including 4xx) means the server is reachable.
90
+ // 5xx means the server responded but errored — we still count it as reachable
91
+ // for the purposes of this connectivity check, but report the code.
92
+ if (status >= 500) {
93
+ return {
94
+ name,
95
+ status: 'fail',
96
+ message: `fail (HTTP ${status})`,
97
+ };
98
+ }
99
+ return {
100
+ name,
101
+ status: 'ok',
102
+ message: `ok (HTTP ${status})`,
103
+ };
104
+ }
105
+ catch (err) {
106
+ if (signal.aborted) {
107
+ throw signal.reason;
108
+ }
109
+ // Check if error carries an HTTP status
110
+ if (err instanceof Error) {
111
+ // HttpError carries a status property
112
+ const httpErr = err;
113
+ if (typeof httpErr.status === 'number') {
114
+ return {
115
+ name,
116
+ status: 'fail',
117
+ message: `fail (HTTP ${httpErr.status})`,
118
+ };
119
+ }
120
+ }
121
+ return {
122
+ name,
123
+ status: 'fail',
124
+ message: 'fail (network)',
125
+ };
126
+ }
127
+ }
128
+ // ---------------------------------------------------------------------------
129
+ // Main runner
130
+ // ---------------------------------------------------------------------------
131
+ export async function runDoctor(deps) {
132
+ const { config, cwd, http, signal } = deps;
133
+ const checks = [];
134
+ // Throw immediately if aborted at entry.
135
+ if (signal.aborted) {
136
+ throw signal.reason;
137
+ }
138
+ // -------------------------------------------------------------------------
139
+ // Optional: clear cache before running checks
140
+ // -------------------------------------------------------------------------
141
+ if (deps.clearCache === true) {
142
+ const cacheDir = resolve(cwd, config.cache.dir);
143
+ try {
144
+ await rm(cacheDir, { recursive: true, force: true });
145
+ checks.push({
146
+ name: 'cache-cleared',
147
+ status: 'ok',
148
+ message: 'Cache directory cleared.',
149
+ });
150
+ }
151
+ catch (err) {
152
+ const msg = err instanceof Error ? err.message : String(err);
153
+ checks.push({
154
+ name: 'cache-cleared',
155
+ status: 'fail',
156
+ message: `Failed to clear cache directory: ${msg}`,
157
+ });
158
+ }
159
+ }
160
+ // Resolve the injectable fs module or fall back to node:fs/promises.
161
+ const fsModule = await resolveFs(deps.fs);
162
+ // -------------------------------------------------------------------------
163
+ // Check 1: Node version
164
+ // -------------------------------------------------------------------------
165
+ {
166
+ const version = process.version; // e.g. "v20.0.0"
167
+ const match = /^v(\d+)/.exec(version);
168
+ const major = match !== null && match[1] !== undefined ? parseInt(match[1], 10) : 0;
169
+ if (major >= MIN_NODE_MAJOR) {
170
+ checks.push({
171
+ name: 'node-version',
172
+ status: 'ok',
173
+ message: `Node ${version} satisfies >=v${MIN_NODE_MAJOR}.`,
174
+ });
175
+ }
176
+ else {
177
+ checks.push({
178
+ name: 'node-version',
179
+ status: 'fail',
180
+ message: `Node ${version} is below the required v${MIN_NODE_MAJOR}.0.0.`,
181
+ });
182
+ }
183
+ }
184
+ // -------------------------------------------------------------------------
185
+ // Check 2: Config validity (always ok — config was already validated by caller)
186
+ // -------------------------------------------------------------------------
187
+ {
188
+ checks.push({
189
+ name: 'config-valid',
190
+ status: 'ok',
191
+ message: 'Configuration is valid.',
192
+ });
193
+ }
194
+ // -------------------------------------------------------------------------
195
+ // Check 3: Bibliography file exists
196
+ // -------------------------------------------------------------------------
197
+ {
198
+ const bibPath = resolve(cwd, config.bibliography.file);
199
+ try {
200
+ await fsModule.access(bibPath);
201
+ checks.push({
202
+ name: 'bibliography-exists',
203
+ status: 'ok',
204
+ message: `Bibliography file found: ${config.bibliography.file}`,
205
+ });
206
+ }
207
+ catch {
208
+ checks.push({
209
+ name: 'bibliography-exists',
210
+ status: 'fail',
211
+ message: `Bibliography file not found: ${config.bibliography.file}`,
212
+ });
213
+ }
214
+ }
215
+ // -------------------------------------------------------------------------
216
+ // Check 4: Bibliography parses as valid CSL-JSON
217
+ // -------------------------------------------------------------------------
218
+ {
219
+ try {
220
+ const entries = await loadBibliography({ path: config.bibliography.file, cwd });
221
+ checks.push({
222
+ name: 'bibliography-parses',
223
+ status: 'ok',
224
+ message: `Bibliography parsed successfully (${entries.length} entries).`,
225
+ details: { entryCount: entries.length },
226
+ });
227
+ }
228
+ catch (err) {
229
+ const msg = err instanceof BibliographyParseError ? err.message : String(err);
230
+ checks.push({
231
+ name: 'bibliography-parses',
232
+ status: 'fail',
233
+ message: `Bibliography parse error: ${msg}`,
234
+ });
235
+ }
236
+ }
237
+ // -------------------------------------------------------------------------
238
+ // Check 5: Phrase denylist (if configured)
239
+ // -------------------------------------------------------------------------
240
+ if (config.phrases.file !== null) {
241
+ try {
242
+ const patterns = await loadDenylist({ path: config.phrases.file, cwd });
243
+ checks.push({
244
+ name: 'phrase-denylist',
245
+ status: 'ok',
246
+ message: `Phrase denylist loaded (${patterns.length} patterns).`,
247
+ details: { patternCount: patterns.length },
248
+ });
249
+ }
250
+ catch (err) {
251
+ if (err instanceof PhraseLoaderError) {
252
+ // Distinguish missing-file (warn) from parse/compile errors (fail).
253
+ const msg = err.message;
254
+ if (msg.includes('not found')) {
255
+ checks.push({
256
+ name: 'phrase-denylist',
257
+ status: 'warn',
258
+ message: `Phrase denylist file configured but not found: ${config.phrases.file}`,
259
+ });
260
+ }
261
+ else {
262
+ checks.push({
263
+ name: 'phrase-denylist',
264
+ status: 'fail',
265
+ message: `Phrase denylist load error: ${msg}`,
266
+ });
267
+ }
268
+ }
269
+ else {
270
+ checks.push({
271
+ name: 'phrase-denylist',
272
+ status: 'fail',
273
+ message: `Phrase denylist load error: ${String(err)}`,
274
+ });
275
+ }
276
+ }
277
+ }
278
+ // -------------------------------------------------------------------------
279
+ // Check 6: Cache directory writability
280
+ // -------------------------------------------------------------------------
281
+ {
282
+ const cacheDir = resolve(cwd, config.cache.dir);
283
+ try {
284
+ await fsModule.access(cacheDir);
285
+ // Directory exists — check if we can read it (proxy for write access in injectable fs)
286
+ checks.push({
287
+ name: 'cache-writable',
288
+ status: 'ok',
289
+ message: `Cache directory is accessible: ${config.cache.dir}`,
290
+ details: { cacheDir },
291
+ });
292
+ }
293
+ catch {
294
+ // Directory does not exist yet — that is fine; bibcheck creates it on first write.
295
+ checks.push({
296
+ name: 'cache-writable',
297
+ status: 'warn',
298
+ message: `Cache directory does not exist yet (will be created on first use): ${config.cache.dir}`,
299
+ details: { cacheDir },
300
+ });
301
+ }
302
+ }
303
+ // -------------------------------------------------------------------------
304
+ // Check 7: Cache size
305
+ // -------------------------------------------------------------------------
306
+ {
307
+ const cacheDir = resolve(cwd, config.cache.dir);
308
+ const totalBytes = await sumDirBytes(cacheDir, fsModule);
309
+ const totalMb = totalBytes / (1024 * 1024);
310
+ const maxMb = config.cache.max_size_mb;
311
+ let cacheStatus = 'ok';
312
+ let cacheMsg;
313
+ if (maxMb !== null && totalMb > maxMb * 0.8) {
314
+ cacheStatus = 'warn';
315
+ cacheMsg = `Cache is using ${totalMb.toFixed(1)} MB of ${maxMb} MB limit (>80%).`;
316
+ }
317
+ else if (maxMb !== null) {
318
+ cacheMsg = `Cache is using ${totalMb.toFixed(1)} MB of ${maxMb} MB limit.`;
319
+ }
320
+ else {
321
+ cacheMsg = `Cache is using ${totalMb.toFixed(1)} MB (no limit configured).`;
322
+ }
323
+ checks.push({
324
+ name: 'cache-size',
325
+ status: cacheStatus,
326
+ message: cacheMsg,
327
+ details: { totalBytes, totalMb: parseFloat(totalMb.toFixed(3)) },
328
+ });
329
+ }
330
+ // -------------------------------------------------------------------------
331
+ // Check 8: CrossRef API connectivity
332
+ // -------------------------------------------------------------------------
333
+ if (signal.aborted)
334
+ throw signal.reason;
335
+ checks.push(await checkApiConnectivity('crossref-connectivity', `${trimTrailingSlash(config.apis.crossref_base ?? API_BASE_DEFAULTS.crossref)}/works/10.1000/xyz123`, http, signal));
336
+ // -------------------------------------------------------------------------
337
+ // Check 9: OpenAlex API connectivity
338
+ // -------------------------------------------------------------------------
339
+ if (signal.aborted)
340
+ throw signal.reason;
341
+ checks.push(await checkApiConnectivity('openalex-connectivity', `${trimTrailingSlash(config.apis.openalex_base ?? API_BASE_DEFAULTS.openalex)}/`, http, signal));
342
+ // -------------------------------------------------------------------------
343
+ // Check 10: OpenLibrary API connectivity
344
+ // -------------------------------------------------------------------------
345
+ if (signal.aborted)
346
+ throw signal.reason;
347
+ checks.push(await checkApiConnectivity('openlibrary-connectivity', `${trimTrailingSlash(config.apis.openlibrary_base ?? API_BASE_DEFAULTS.openlibrary)}/api/books`, http, signal));
348
+ // -------------------------------------------------------------------------
349
+ // Check 11: Trusted host whitelist sanity
350
+ // -------------------------------------------------------------------------
351
+ {
352
+ const hosts = config.trusted_hosts.hosts;
353
+ if (hosts.length === 0) {
354
+ checks.push({
355
+ name: 'trusted-hosts',
356
+ status: 'warn',
357
+ message: 'trusted_hosts.hosts is empty — all canonical URL checks will be skipped.',
358
+ });
359
+ }
360
+ else {
361
+ const invalid = hosts.filter((h) => !HOSTNAME_RE.test(h));
362
+ if (invalid.length > 0) {
363
+ checks.push({
364
+ name: 'trusted-hosts',
365
+ status: 'warn',
366
+ message: `Some trusted hosts do not look like valid hostnames: ${invalid.join(', ')}`,
367
+ details: { invalidHosts: invalid },
368
+ });
369
+ }
370
+ else {
371
+ checks.push({
372
+ name: 'trusted-hosts',
373
+ status: 'ok',
374
+ message: `Trusted host whitelist is valid (${hosts.length} hosts).`,
375
+ details: { hostCount: hosts.length },
376
+ });
377
+ }
378
+ }
379
+ }
380
+ // -------------------------------------------------------------------------
381
+ // Aggregate result
382
+ // -------------------------------------------------------------------------
383
+ const ok = checks.every((c) => c.status !== 'fail');
384
+ return { checks, ok };
385
+ }
386
+ //# sourceMappingURL=doctor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"doctor.js","sourceRoot":"","sources":["../src/doctor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAGH,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAEhD,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,EAAE,EAAE,MAAM,kBAAkB,CAAC;AACtC,OAAO,EAAE,gBAAgB,EAAE,sBAAsB,EAAE,MAAM,iBAAiB,CAAC;AAC3E,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAiCpE,8EAA8E;AAC9E,mBAAmB;AACnB,8EAA8E;AAE9E,4CAA4C;AAC5C,MAAM,cAAc,GAAG,EAAE,CAAC;AAE1B,wEAAwE;AACxE,SAAS,iBAAiB,CAAC,GAAW;IACpC,OAAO,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC;AACpD,CAAC;AAED,2CAA2C;AAC3C,MAAM,WAAW,GAAG,0BAA0B,CAAC;AAI/C,6EAA6E;AAC7E,KAAK,UAAU,SAAS,CAAC,KAA0B;IACjD,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,KAAK,CAAC;IACtC,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,kBAAkB,CAAC,CAAC;IAChD,OAAO;QACL,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC;QAC9B,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC;QAClC,OAAO,EAAE,CAAC,CAAS,EAAE,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC;KAC1C,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,KAAK,UAAU,WAAW,CACxB,OAAe,EACf,QAAqB;IAErB,IAAI,CAAC;QACH,MAAM,QAAQ,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IACjC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,CAAC,CAAC;IACX,CAAC;IAED,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,MAAM,KAAK,GAAa,CAAC,OAAO,CAAC,CAAC;IAClC,OAAO,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACxB,MAAM,OAAO,GAAG,KAAK,CAAC,GAAG,EAAG,CAAC;QAC7B,IAAI,OAAiB,CAAC;QACtB,IAAI,CAAC;YACH,OAAO,GAAG,MAAM,QAAQ,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;QAC5C,CAAC;QAAC,MAAM,CAAC;YACP,SAAS;QACX,CAAC;QACD,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;YAC5B,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;YACvC,IAAI,CAAC;gBACH,MAAM,CAAC,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;gBACzC,IAAI,CAAC,CAAC,WAAW,EAAE,EAAE,CAAC;oBACpB,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;gBACxB,CAAC;qBAAM,CAAC;oBACN,KAAK,IAAI,CAAC,CAAC,IAAI,CAAC;gBAClB,CAAC;YACH,CAAC;YAAC,MAAM,CAAC;gBACP,4BAA4B;YAC9B,CAAC;QACH,CAAC;IACH,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,4DAA4D;AAC5D,KAAK,UAAU,oBAAoB,CACjC,IAAY,EACZ,GAAW,EACX,IAAgB,EAChB,MAAmB;IAEnB,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;QACnB,MAAM,MAAM,CAAC,MAAe,CAAC;IAC/B,CAAC;IACD,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,IAAI,EAAE,eAAe,EAAE,KAAK,EAAE,CAAC,CAAC;QAC3F,MAAM,MAAM,GAAG,QAAQ,CAAC,MAAM,CAAC;QAC/B,8DAA8D;QAC9D,8EAA8E;QAC9E,oEAAoE;QACpE,IAAI,MAAM,IAAI,GAAG,EAAE,CAAC;YAClB,OAAO;gBACL,IAAI;gBACJ,MAAM,EAAE,MAAM;gBACd,OAAO,EAAE,cAAc,MAAM,GAAG;aACjC,CAAC;QACJ,CAAC;QACD,OAAO;YACL,IAAI;YACJ,MAAM,EAAE,IAAI;YACZ,OAAO,EAAE,YAAY,MAAM,GAAG;SAC/B,CAAC;IACJ,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;YACnB,MAAM,MAAM,CAAC,MAAe,CAAC;QAC/B,CAAC;QACD,wCAAwC;QACxC,IAAI,GAAG,YAAY,KAAK,EAAE,CAAC;YACzB,sCAAsC;YACtC,MAAM,OAAO,GAAG,GAAkC,CAAC;YACnD,IAAI,OAAO,OAAO,CAAC,MAAM,KAAK,QAAQ,EAAE,CAAC;gBACvC,OAAO;oBACL,IAAI;oBACJ,MAAM,EAAE,MAAM;oBACd,OAAO,EAAE,cAAc,OAAO,CAAC,MAAM,GAAG;iBACzC,CAAC;YACJ,CAAC;QACH,CAAC;QACD,OAAO;YACL,IAAI;YACJ,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,gBAAgB;SAC1B,CAAC;IACJ,CAAC;AACH,CAAC;AAED,8EAA8E;AAC9E,cAAc;AACd,8EAA8E;AAE9E,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,IAAmB;IACjD,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;IAC3C,MAAM,MAAM,GAAkB,EAAE,CAAC;IAEjC,yCAAyC;IACzC,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;QACnB,MAAM,MAAM,CAAC,MAAe,CAAC;IAC/B,CAAC;IAED,4EAA4E;IAC5E,8CAA8C;IAC9C,4EAA4E;IAE5E,IAAI,IAAI,CAAC,UAAU,KAAK,IAAI,EAAE,CAAC;QAC7B,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,EAAE,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAChD,IAAI,CAAC;YACH,MAAM,EAAE,CAAC,QAAQ,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;YACrD,MAAM,CAAC,IAAI,CAAC;gBACV,IAAI,EAAE,eAAe;gBACrB,MAAM,EAAE,IAAI;gBACZ,OAAO,EAAE,0BAA0B;aACpC,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,GAAG,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YAC7D,MAAM,CAAC,IAAI,CAAC;gBACV,IAAI,EAAE,eAAe;gBACrB,MAAM,EAAE,MAAM;gBACd,OAAO,EAAE,oCAAoC,GAAG,EAAE;aACnD,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,qEAAqE;IACrE,MAAM,QAAQ,GAAG,MAAM,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAE1C,4EAA4E;IAC5E,wBAAwB;IACxB,4EAA4E;IAE5E,CAAC;QACC,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC,iBAAiB;QAClD,MAAM,KAAK,GAAG,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACtC,MAAM,KAAK,GAAG,KAAK,KAAK,IAAI,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACpF,IAAI,KAAK,IAAI,cAAc,EAAE,CAAC;YAC5B,MAAM,CAAC,IAAI,CAAC;gBACV,IAAI,EAAE,cAAc;gBACpB,MAAM,EAAE,IAAI;gBACZ,OAAO,EAAE,QAAQ,OAAO,iBAAiB,cAAc,GAAG;aAC3D,CAAC,CAAC;QACL,CAAC;aAAM,CAAC;YACN,MAAM,CAAC,IAAI,CAAC;gBACV,IAAI,EAAE,cAAc;gBACpB,MAAM,EAAE,MAAM;gBACd,OAAO,EAAE,QAAQ,OAAO,2BAA2B,cAAc,OAAO;aACzE,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,4EAA4E;IAC5E,gFAAgF;IAChF,4EAA4E;IAE5E,CAAC;QACC,MAAM,CAAC,IAAI,CAAC;YACV,IAAI,EAAE,cAAc;YACpB,MAAM,EAAE,IAAI;YACZ,OAAO,EAAE,yBAAyB;SACnC,CAAC,CAAC;IACL,CAAC;IAED,4EAA4E;IAC5E,oCAAoC;IACpC,4EAA4E;IAE5E,CAAC;QACC,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,EAAE,MAAM,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC;QACvD,IAAI,CAAC;YACH,MAAM,QAAQ,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;YAC/B,MAAM,CAAC,IAAI,CAAC;gBACV,IAAI,EAAE,qBAAqB;gBAC3B,MAAM,EAAE,IAAI;gBACZ,OAAO,EAAE,4BAA4B,MAAM,CAAC,YAAY,CAAC,IAAI,EAAE;aAChE,CAAC,CAAC;QACL,CAAC;QAAC,MAAM,CAAC;YACP,MAAM,CAAC,IAAI,CAAC;gBACV,IAAI,EAAE,qBAAqB;gBAC3B,MAAM,EAAE,MAAM;gBACd,OAAO,EAAE,gCAAgC,MAAM,CAAC,YAAY,CAAC,IAAI,EAAE;aACpE,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,4EAA4E;IAC5E,iDAAiD;IACjD,4EAA4E;IAE5E,CAAC;QACC,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,MAAM,gBAAgB,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,YAAY,CAAC,IAAI,EAAE,GAAG,EAAE,CAAC,CAAC;YAChF,MAAM,CAAC,IAAI,CAAC;gBACV,IAAI,EAAE,qBAAqB;gBAC3B,MAAM,EAAE,IAAI;gBACZ,OAAO,EAAE,qCAAqC,OAAO,CAAC,MAAM,YAAY;gBACxE,OAAO,EAAE,EAAE,UAAU,EAAE,OAAO,CAAC,MAAM,EAAE;aACxC,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,GAAG,GAAG,GAAG,YAAY,sBAAsB,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YAC9E,MAAM,CAAC,IAAI,CAAC;gBACV,IAAI,EAAE,qBAAqB;gBAC3B,MAAM,EAAE,MAAM;gBACd,OAAO,EAAE,6BAA6B,GAAG,EAAE;aAC5C,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,4EAA4E;IAC5E,2CAA2C;IAC3C,4EAA4E;IAE5E,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,KAAK,IAAI,EAAE,CAAC;QACjC,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,MAAM,YAAY,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,EAAE,CAAC,CAAC;YACxE,MAAM,CAAC,IAAI,CAAC;gBACV,IAAI,EAAE,iBAAiB;gBACvB,MAAM,EAAE,IAAI;gBACZ,OAAO,EAAE,2BAA2B,QAAQ,CAAC,MAAM,aAAa;gBAChE,OAAO,EAAE,EAAE,YAAY,EAAE,QAAQ,CAAC,MAAM,EAAE;aAC3C,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAI,GAAG,YAAY,iBAAiB,EAAE,CAAC;gBACrC,oEAAoE;gBACpE,MAAM,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC;gBACxB,IAAI,GAAG,CAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC;oBAC9B,MAAM,CAAC,IAAI,CAAC;wBACV,IAAI,EAAE,iBAAiB;wBACvB,MAAM,EAAE,MAAM;wBACd,OAAO,EAAE,kDAAkD,MAAM,CAAC,OAAO,CAAC,IAAI,EAAE;qBACjF,CAAC,CAAC;gBACL,CAAC;qBAAM,CAAC;oBACN,MAAM,CAAC,IAAI,CAAC;wBACV,IAAI,EAAE,iBAAiB;wBACvB,MAAM,EAAE,MAAM;wBACd,OAAO,EAAE,+BAA+B,GAAG,EAAE;qBAC9C,CAAC,CAAC;gBACL,CAAC;YACH,CAAC;iBAAM,CAAC;gBACN,MAAM,CAAC,IAAI,CAAC;oBACV,IAAI,EAAE,iBAAiB;oBACvB,MAAM,EAAE,MAAM;oBACd,OAAO,EAAE,+BAA+B,MAAM,CAAC,GAAG,CAAC,EAAE;iBACtD,CAAC,CAAC;YACL,CAAC;QACH,CAAC;IACH,CAAC;IAED,4EAA4E;IAC5E,uCAAuC;IACvC,4EAA4E;IAE5E,CAAC;QACC,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,EAAE,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAChD,IAAI,CAAC;YACH,MAAM,QAAQ,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;YAChC,uFAAuF;YACvF,MAAM,CAAC,IAAI,CAAC;gBACV,IAAI,EAAE,gBAAgB;gBACtB,MAAM,EAAE,IAAI;gBACZ,OAAO,EAAE,kCAAkC,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE;gBAC7D,OAAO,EAAE,EAAE,QAAQ,EAAE;aACtB,CAAC,CAAC;QACL,CAAC;QAAC,MAAM,CAAC;YACP,mFAAmF;YACnF,MAAM,CAAC,IAAI,CAAC;gBACV,IAAI,EAAE,gBAAgB;gBACtB,MAAM,EAAE,MAAM;gBACd,OAAO,EAAE,sEAAsE,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE;gBACjG,OAAO,EAAE,EAAE,QAAQ,EAAE;aACtB,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,4EAA4E;IAC5E,sBAAsB;IACtB,4EAA4E;IAE5E,CAAC;QACC,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,EAAE,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAChD,MAAM,UAAU,GAAG,MAAM,WAAW,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;QACzD,MAAM,OAAO,GAAG,UAAU,GAAG,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC;QAC3C,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,WAAW,CAAC;QAEvC,IAAI,WAAW,GAAkB,IAAI,CAAC;QACtC,IAAI,QAAgB,CAAC;QAErB,IAAI,KAAK,KAAK,IAAI,IAAI,OAAO,GAAG,KAAK,GAAG,GAAG,EAAE,CAAC;YAC5C,WAAW,GAAG,MAAM,CAAC;YACrB,QAAQ,GAAG,kBAAkB,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,UAAU,KAAK,mBAAmB,CAAC;QACpF,CAAC;aAAM,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;YAC1B,QAAQ,GAAG,kBAAkB,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,UAAU,KAAK,YAAY,CAAC;QAC7E,CAAC;aAAM,CAAC;YACN,QAAQ,GAAG,kBAAkB,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,4BAA4B,CAAC;QAC9E,CAAC;QAED,MAAM,CAAC,IAAI,CAAC;YACV,IAAI,EAAE,YAAY;YAClB,MAAM,EAAE,WAAW;YACnB,OAAO,EAAE,QAAQ;YACjB,OAAO,EAAE,EAAE,UAAU,EAAE,OAAO,EAAE,UAAU,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE;SACjE,CAAC,CAAC;IACL,CAAC;IAED,4EAA4E;IAC5E,qCAAqC;IACrC,4EAA4E;IAE5E,IAAI,MAAM,CAAC,OAAO;QAAE,MAAM,MAAM,CAAC,MAAe,CAAC;IAEjD,MAAM,CAAC,IAAI,CACT,MAAM,oBAAoB,CACxB,uBAAuB,EACvB,GAAG,iBAAiB,CAAC,MAAM,CAAC,IAAI,CAAC,aAAa,IAAI,iBAAiB,CAAC,QAAQ,CAAC,uBAAuB,EACpG,IAAI,EACJ,MAAM,CACP,CACF,CAAC;IAEF,4EAA4E;IAC5E,qCAAqC;IACrC,4EAA4E;IAE5E,IAAI,MAAM,CAAC,OAAO;QAAE,MAAM,MAAM,CAAC,MAAe,CAAC;IAEjD,MAAM,CAAC,IAAI,CACT,MAAM,oBAAoB,CACxB,uBAAuB,EACvB,GAAG,iBAAiB,CAAC,MAAM,CAAC,IAAI,CAAC,aAAa,IAAI,iBAAiB,CAAC,QAAQ,CAAC,GAAG,EAChF,IAAI,EACJ,MAAM,CACP,CACF,CAAC;IAEF,4EAA4E;IAC5E,yCAAyC;IACzC,4EAA4E;IAE5E,IAAI,MAAM,CAAC,OAAO;QAAE,MAAM,MAAM,CAAC,MAAe,CAAC;IAEjD,MAAM,CAAC,IAAI,CACT,MAAM,oBAAoB,CACxB,0BAA0B,EAC1B,GAAG,iBAAiB,CAAC,MAAM,CAAC,IAAI,CAAC,gBAAgB,IAAI,iBAAiB,CAAC,WAAW,CAAC,YAAY,EAC/F,IAAI,EACJ,MAAM,CACP,CACF,CAAC;IAEF,4EAA4E;IAC5E,0CAA0C;IAC1C,4EAA4E;IAE5E,CAAC;QACC,MAAM,KAAK,GAAG,MAAM,CAAC,aAAa,CAAC,KAAK,CAAC;QACzC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACvB,MAAM,CAAC,IAAI,CAAC;gBACV,IAAI,EAAE,eAAe;gBACrB,MAAM,EAAE,MAAM;gBACd,OAAO,EAAE,0EAA0E;aACpF,CAAC,CAAC;QACL,CAAC;aAAM,CAAC;YACN,MAAM,OAAO,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;YAC1D,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBACvB,MAAM,CAAC,IAAI,CAAC;oBACV,IAAI,EAAE,eAAe;oBACrB,MAAM,EAAE,MAAM;oBACd,OAAO,EAAE,wDAAwD,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE;oBACrF,OAAO,EAAE,EAAE,YAAY,EAAE,OAAO,EAAE;iBACnC,CAAC,CAAC;YACL,CAAC;iBAAM,CAAC;gBACN,MAAM,CAAC,IAAI,CAAC;oBACV,IAAI,EAAE,eAAe;oBACrB,MAAM,EAAE,IAAI;oBACZ,OAAO,EAAE,oCAAoC,KAAK,CAAC,MAAM,UAAU;oBACnE,OAAO,EAAE,EAAE,SAAS,EAAE,KAAK,CAAC,MAAM,EAAE;iBACrC,CAAC,CAAC;YACL,CAAC;QACH,CAAC;IACH,CAAC;IAED,4EAA4E;IAC5E,mBAAmB;IACnB,4EAA4E;IAE5E,MAAM,EAAE,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,MAAM,CAAC,CAAC;IACpD,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC;AACxB,CAAC"}
@@ -0,0 +1,70 @@
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 type { CslEntry } from './schema/csl.js';
31
+ import type { ExistenceLayer } from './schema/output.js';
32
+ import type { CrossRefClient } from './databases/crossref.js';
33
+ import type { OpenAlexClient } from './databases/openalex.js';
34
+ import type { OpenLibraryClient } from './databases/openlibrary.js';
35
+ export interface RunExistenceDeps {
36
+ bibliography: CslEntry[];
37
+ clients: {
38
+ crossref: CrossRefClient;
39
+ openalex: OpenAlexClient;
40
+ openlibrary: OpenLibraryClient;
41
+ };
42
+ /**
43
+ * Citekeys whose local identifier validation (T21) failed
44
+ * (malformed/bad-checksum DOI or ISBN). These SKIP the network existence
45
+ * call and are recorded as `unverifiable` — a malformed identifier cannot
46
+ * be looked up and is already a gating finding via
47
+ * `summary.malformedIdentifiers`.
48
+ */
49
+ identifierInvalid?: ReadonlySet<string>;
50
+ signal: AbortSignal;
51
+ }
52
+ export interface RunExistenceResult {
53
+ entries: Array<Pick<{
54
+ citekey: string;
55
+ existence: ExistenceLayer;
56
+ }, 'citekey' | 'existence'>>;
57
+ }
58
+ export declare function getFirstAuthorSurname(entry: CslEntry): string | undefined;
59
+ export declare function getAllAuthorNames(entry: CslEntry): string[];
60
+ /**
61
+ * Returns true if the two title strings are close enough to be the same work.
62
+ *
63
+ * Primary metric is the token-set ratio (≥ 0.85), which is robust to subtitle
64
+ * presence and word reordering. As a tiebreaker (e.g. single-token titles with
65
+ * a small typo, where the token sets are disjoint but the strings are close),
66
+ * the normalised Levenshtein ratio (≥ 0.85) is consulted.
67
+ */
68
+ export declare function titlesMatch(a: string, b: string): boolean;
69
+ export declare function runExistence(deps: RunExistenceDeps): Promise<RunExistenceResult>;
70
+ //# sourceMappingURL=existence.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"existence.d.ts","sourceRoot":"","sources":["../src/existence.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAIH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,KAAK,EAIV,cAAc,EAEf,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAC9D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAC9D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AAOpE,MAAM,WAAW,gBAAgB;IAC/B,YAAY,EAAE,QAAQ,EAAE,CAAC;IACzB,OAAO,EAAE;QACP,QAAQ,EAAE,cAAc,CAAC;QACzB,QAAQ,EAAE,cAAc,CAAC;QACzB,WAAW,EAAE,iBAAiB,CAAC;KAChC,CAAC;IACF;;;;;;OAMG;IACH,iBAAiB,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IACxC,MAAM,EAAE,WAAW,CAAC;CACrB;AAED,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,KAAK,CAAC,IAAI,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,cAAc,CAAA;KAAE,EAAE,SAAS,GAAG,WAAW,CAAC,CAAC,CAAC;CAC/F;AAMD,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,QAAQ,GAAG,MAAM,GAAG,SAAS,CAIzE;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,QAAQ,GAAG,MAAM,EAAE,CAE3D;AA0CD;;;;;;;GAOG;AACH,wBAAgB,WAAW,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO,CAgBzD;AA2OD,wBAAsB,YAAY,CAAC,IAAI,EAAE,gBAAgB,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAiBtF"}