open-museum-mcp 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 +216 -0
- package/dist/cite.d.ts +17 -0
- package/dist/cite.js +61 -0
- package/dist/cite.js.map +1 -0
- package/dist/data/dynasties.json +93 -0
- package/dist/data/regions.json +21 -0
- package/dist/dateParser.d.ts +17 -0
- package/dist/dateParser.js +265 -0
- package/dist/dateParser.js.map +1 -0
- package/dist/db.d.ts +35 -0
- package/dist/db.js +182 -0
- package/dist/db.js.map +1 -0
- package/dist/fetchers/met.d.ts +2 -0
- package/dist/fetchers/met.js +132 -0
- package/dist/fetchers/met.js.map +1 -0
- package/dist/fetchers/types.d.ts +11 -0
- package/dist/fetchers/types.js +2 -0
- package/dist/fetchers/types.js.map +1 -0
- package/dist/licenseGate.d.ts +13 -0
- package/dist/licenseGate.js +112 -0
- package/dist/licenseGate.js.map +1 -0
- package/dist/mappings.d.ts +3 -0
- package/dist/mappings.js +36 -0
- package/dist/mappings.js.map +1 -0
- package/dist/server.d.ts +2 -0
- package/dist/server.js +243 -0
- package/dist/server.js.map +1 -0
- package/dist/types.d.ts +88 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +58 -0
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
import dynastiesData from './data/dynasties.json' with { type: 'json' };
|
|
2
|
+
const dynasties = dynastiesData;
|
|
3
|
+
const flatDynasties = (() => {
|
|
4
|
+
const out = {};
|
|
5
|
+
const seen = new Set();
|
|
6
|
+
for (const culture of Object.keys(dynasties)) {
|
|
7
|
+
for (const [period, range] of Object.entries(dynasties[culture])) {
|
|
8
|
+
const key = period.toLowerCase();
|
|
9
|
+
if (seen.has(key)) {
|
|
10
|
+
console.warn(`[open-museum-mcp] dynasty table: duplicate period "${key}" across cultures; last definition wins`);
|
|
11
|
+
}
|
|
12
|
+
seen.add(key);
|
|
13
|
+
out[key] = range;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
return out;
|
|
17
|
+
})();
|
|
18
|
+
const ROMAN = {
|
|
19
|
+
i: 1, ii: 2, iii: 3, iv: 4, v: 5, vi: 6, vii: 7, viii: 8, ix: 9, x: 10,
|
|
20
|
+
xi: 11, xii: 12, xiii: 13, xiv: 14, xv: 15, xvi: 16, xvii: 17, xviii: 18,
|
|
21
|
+
xix: 19, xx: 20, xxi: 21,
|
|
22
|
+
};
|
|
23
|
+
const ORDINAL_WORDS = {
|
|
24
|
+
first: 1, second: 2, third: 3, fourth: 4, fifth: 5, sixth: 6, seventh: 7,
|
|
25
|
+
eighth: 8, ninth: 9, tenth: 10, eleventh: 11, twelfth: 12, thirteenth: 13,
|
|
26
|
+
fourteenth: 14, fifteenth: 15, sixteenth: 16, seventeenth: 17,
|
|
27
|
+
eighteenth: 18, nineteenth: 19, twentieth: 20, 'twenty-first': 21,
|
|
28
|
+
};
|
|
29
|
+
function ordinalToNumber(token) {
|
|
30
|
+
const lower = token.toLowerCase();
|
|
31
|
+
// Check the word dictionary BEFORE stripping suffixes — otherwise hyphenated
|
|
32
|
+
// forms like "twenty-first" get mangled into "twenty-fir" by the suffix
|
|
33
|
+
// regex and never match.
|
|
34
|
+
if (ORDINAL_WORDS[lower] !== undefined)
|
|
35
|
+
return ORDINAL_WORDS[lower];
|
|
36
|
+
const stripped = lower.replace(/(st|nd|rd|th)$/, '');
|
|
37
|
+
if (/^\d+$/.test(stripped))
|
|
38
|
+
return parseInt(stripped, 10);
|
|
39
|
+
if (ROMAN[stripped] !== undefined)
|
|
40
|
+
return ROMAN[stripped];
|
|
41
|
+
return ORDINAL_WORDS[stripped] ?? null;
|
|
42
|
+
}
|
|
43
|
+
// 19th century CE = 1801–1900 (no year zero, century N spans years
|
|
44
|
+
// (N-1)*100+1 to N*100). qualifier divides the span into thirds; floor()
|
|
45
|
+
// accepts that the 99-year span doesn't divide evenly — close enough for
|
|
46
|
+
// catalog metadata and matches museum convention.
|
|
47
|
+
function centuryRange(n, era, qualifier) {
|
|
48
|
+
let start, end;
|
|
49
|
+
if (era === 'ce') {
|
|
50
|
+
start = (n - 1) * 100 + 1;
|
|
51
|
+
end = n * 100;
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
end = -((n - 1) * 100 + 1);
|
|
55
|
+
start = -(n * 100);
|
|
56
|
+
}
|
|
57
|
+
const span = end - start;
|
|
58
|
+
if (qualifier === 'early') {
|
|
59
|
+
return { yearStart: start, yearEnd: start + Math.floor(span / 3) };
|
|
60
|
+
}
|
|
61
|
+
if (qualifier === 'mid' || qualifier === 'middle') {
|
|
62
|
+
return {
|
|
63
|
+
yearStart: start + Math.floor(span / 3),
|
|
64
|
+
yearEnd: end - Math.floor(span / 3),
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
if (qualifier === 'late') {
|
|
68
|
+
return { yearStart: end - Math.floor(span / 3), yearEnd: end };
|
|
69
|
+
}
|
|
70
|
+
return { yearStart: start, yearEnd: end };
|
|
71
|
+
}
|
|
72
|
+
// True only when the string carries BOTH a BCE marker and a CE marker —
|
|
73
|
+
// signals a cross-era range that tryRangeRegex must defer on so
|
|
74
|
+
// tryCrossEraRange can handle it correctly.
|
|
75
|
+
function hasMixedEras(s) {
|
|
76
|
+
const hasBce = /\b(b\.?c\.?e?\.?|bc)\b/i.test(s);
|
|
77
|
+
if (!hasBce)
|
|
78
|
+
return false;
|
|
79
|
+
return /\bce\b/i.test(s) || /\bc\.e\./i.test(s);
|
|
80
|
+
}
|
|
81
|
+
function tryCrossEraRange(s) {
|
|
82
|
+
const m = s.match(/(\d{1,5})\s*(?:b\.?c\.?e?\.?|bc)\s*[-–]\s*(\d{1,5})\s*(?:c\.?e\.?|ce)/i);
|
|
83
|
+
if (m) {
|
|
84
|
+
return {
|
|
85
|
+
yearStart: -parseInt(m[1], 10),
|
|
86
|
+
yearEnd: parseInt(m[2], 10),
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
// Numeric range with optional trailing BCE marker. Bare digits separated by
|
|
92
|
+
// hyphen/en-dash. Note: ordinal-century inputs like "14th-15th century" are
|
|
93
|
+
// NOT matched here because the regex requires the dash to immediately follow
|
|
94
|
+
// digits — the "th" between "14" and "-" breaks the match. Don't relax the
|
|
95
|
+
// regex without locking down that invariant in tests; tryCenturyRange owns
|
|
96
|
+
// ordinal-century parsing.
|
|
97
|
+
function tryRangeRegex(s) {
|
|
98
|
+
if (hasMixedEras(s))
|
|
99
|
+
return null;
|
|
100
|
+
const m = s.match(/(-?\d{1,5})\s*[-–]\s*(-?\d{1,5})\s*(b\.?c\.?e?\.?|bc)?/i);
|
|
101
|
+
if (m) {
|
|
102
|
+
const firstStr = m[1];
|
|
103
|
+
const secondStr = m[2];
|
|
104
|
+
let a = parseInt(firstStr, 10);
|
|
105
|
+
let b = parseInt(secondStr, 10);
|
|
106
|
+
const firstIsCleanFourDigit = !firstStr.startsWith('-') && firstStr.length === 4;
|
|
107
|
+
const secondIsShortSuffix = !secondStr.startsWith('-') && (secondStr.length === 1 || secondStr.length === 2);
|
|
108
|
+
// Short-suffix forms expand the second number by reusing digits from the
|
|
109
|
+
// first: "1820-5" → 1820–1825 (decade rollover), "1899–05" → 1899–1905
|
|
110
|
+
// (century rollover). When the candidate would land before the start
|
|
111
|
+
// year, bump it forward by the rollover unit.
|
|
112
|
+
if (!m[3] && firstIsCleanFourDigit && secondIsShortSuffix) {
|
|
113
|
+
const decade = Math.floor(a / 10) * 10;
|
|
114
|
+
const century = Math.floor(a / 100) * 100;
|
|
115
|
+
const candidateDecade = decade + b;
|
|
116
|
+
const candidateCentury = century + b;
|
|
117
|
+
let resolved;
|
|
118
|
+
if (secondStr.length === 1) {
|
|
119
|
+
resolved = candidateDecade < a ? candidateDecade + 10 : candidateDecade;
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
resolved = candidateCentury < a ? candidateCentury + 100 : candidateCentury;
|
|
123
|
+
}
|
|
124
|
+
return { yearStart: a, yearEnd: resolved };
|
|
125
|
+
}
|
|
126
|
+
if (m[3]) {
|
|
127
|
+
a = -Math.abs(a);
|
|
128
|
+
b = -Math.abs(b);
|
|
129
|
+
}
|
|
130
|
+
return { yearStart: Math.min(a, b), yearEnd: Math.max(a, b) };
|
|
131
|
+
}
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
function tryCenturyRange(s) {
|
|
135
|
+
const m = s.match(/([\w-]+)\s*[-–]\s*([\w-]+)\s*(?:-|\s)?\s*century\s*(b\.?c\.?e?\.?|bc)?/i);
|
|
136
|
+
if (!m)
|
|
137
|
+
return null;
|
|
138
|
+
const startN = ordinalToNumber(m[1]);
|
|
139
|
+
const endN = ordinalToNumber(m[2]);
|
|
140
|
+
if (startN === null || endN === null)
|
|
141
|
+
return null;
|
|
142
|
+
const era = m[3] ? 'bce' : 'ce';
|
|
143
|
+
const startRange = centuryRange(startN, era);
|
|
144
|
+
const endRange = centuryRange(endN, era);
|
|
145
|
+
return {
|
|
146
|
+
yearStart: Math.min(startRange.yearStart, endRange.yearStart),
|
|
147
|
+
yearEnd: Math.max(startRange.yearEnd, endRange.yearEnd),
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
function tryDecade(s) {
|
|
151
|
+
const m = s.match(/(?<![\d-])(\d{3,4})s\b/i);
|
|
152
|
+
if (m) {
|
|
153
|
+
const y = parseInt(m[1], 10);
|
|
154
|
+
return { yearStart: y, yearEnd: y + 9 };
|
|
155
|
+
}
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
function trySingleYear(s) {
|
|
159
|
+
const bce = s.match(/(\d{1,5})\s*(b\.?c\.?e?\.?|bc)\b/i);
|
|
160
|
+
if (bce) {
|
|
161
|
+
const y = -parseInt(bce[1], 10);
|
|
162
|
+
return { yearStart: y, yearEnd: y };
|
|
163
|
+
}
|
|
164
|
+
const ce = s.match(/(?<!\w)(\d{1,5})\s*(c\.?e\.?|ce)\b/i);
|
|
165
|
+
if (ce) {
|
|
166
|
+
const y = parseInt(ce[1], 10);
|
|
167
|
+
return { yearStart: y, yearEnd: y };
|
|
168
|
+
}
|
|
169
|
+
const circa = s.match(/(?:c\.?|ca\.?|circa|approximately|around|about)\s*(-?\d{1,5})/i);
|
|
170
|
+
if (circa) {
|
|
171
|
+
const y = parseInt(circa[1], 10);
|
|
172
|
+
return { yearStart: y - 5, yearEnd: y + 5 };
|
|
173
|
+
}
|
|
174
|
+
const exact = s.match(/(?<![\d-])(\d{3,4})(?![\d-])/);
|
|
175
|
+
if (exact) {
|
|
176
|
+
const y = parseInt(exact[1], 10);
|
|
177
|
+
return { yearStart: y, yearEnd: y };
|
|
178
|
+
}
|
|
179
|
+
return null;
|
|
180
|
+
}
|
|
181
|
+
function tryCentury(s) {
|
|
182
|
+
const qual = s.match(/\b(early|mid|middle|late)\b/i);
|
|
183
|
+
const qualifier = qual ? qual[1].toLowerCase() : undefined;
|
|
184
|
+
const stripped = s.replace(/\b(early|mid|middle|late)\b[\s\-]*/i, ' ').trim();
|
|
185
|
+
const m = stripped.match(/([\w-]+)\s*(?:-|\s)?\s*century\s*(b\.?c\.?e?\.?|bc)?/i);
|
|
186
|
+
if (!m)
|
|
187
|
+
return null;
|
|
188
|
+
const num = ordinalToNumber(m[1]);
|
|
189
|
+
if (num === null)
|
|
190
|
+
return null;
|
|
191
|
+
const era = m[2] ? 'bce' : 'ce';
|
|
192
|
+
return centuryRange(num, era, qualifier);
|
|
193
|
+
}
|
|
194
|
+
function tryDynasty(s) {
|
|
195
|
+
const lower = s.toLowerCase();
|
|
196
|
+
const sorted = Object.keys(flatDynasties).sort((a, b) => b.length - a.length);
|
|
197
|
+
for (const period of sorted) {
|
|
198
|
+
if (lower.includes(period)) {
|
|
199
|
+
const [start, end] = flatDynasties[period];
|
|
200
|
+
const qual = lower.match(/\b(early|mid|middle|late)\b/);
|
|
201
|
+
const qualifier = qual ? qual[1] : undefined;
|
|
202
|
+
const span = end - start;
|
|
203
|
+
if (qualifier === 'early') {
|
|
204
|
+
return { yearStart: start, yearEnd: start + Math.floor(span / 3) };
|
|
205
|
+
}
|
|
206
|
+
if (qualifier === 'mid' || qualifier === 'middle') {
|
|
207
|
+
return {
|
|
208
|
+
yearStart: start + Math.floor(span / 3),
|
|
209
|
+
yearEnd: end - Math.floor(span / 3),
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
if (qualifier === 'late') {
|
|
213
|
+
return { yearStart: end - Math.floor(span / 3), yearEnd: end };
|
|
214
|
+
}
|
|
215
|
+
return { yearStart: start, yearEnd: end };
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
return null;
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Parse a museum-supplied display date into a {yearStart, yearEnd} range.
|
|
222
|
+
*
|
|
223
|
+
* Strategies are tried in this exact order — earlier strategies win:
|
|
224
|
+
* 1. cross-era range ("500 BCE – 50 CE")
|
|
225
|
+
* 2. numeric range ("1820–1830", "1820-5", "1899–05")
|
|
226
|
+
* 3. ordinal-century range ("14th-15th century")
|
|
227
|
+
* 4. ordinal century with optional early/mid/late qualifier
|
|
228
|
+
* 5. decade ("1820s")
|
|
229
|
+
* 6. single year ("1888", "ca. 1820", "500 BCE")
|
|
230
|
+
* 7. dynasty/period lookup (longest key first to avoid prefix shadowing)
|
|
231
|
+
*
|
|
232
|
+
* Returns {null, null} when nothing matches — never guesses. BCE is encoded
|
|
233
|
+
* as negative integers so range arithmetic Just Works.
|
|
234
|
+
*/
|
|
235
|
+
export function parseDisplayDate(input) {
|
|
236
|
+
if (!input || typeof input !== 'string') {
|
|
237
|
+
return { yearStart: null, yearEnd: null };
|
|
238
|
+
}
|
|
239
|
+
const s = input.trim();
|
|
240
|
+
if (!s)
|
|
241
|
+
return { yearStart: null, yearEnd: null };
|
|
242
|
+
const cross = tryCrossEraRange(s);
|
|
243
|
+
if (cross)
|
|
244
|
+
return cross;
|
|
245
|
+
const range = tryRangeRegex(s);
|
|
246
|
+
if (range)
|
|
247
|
+
return range;
|
|
248
|
+
const centuryRangeMatch = tryCenturyRange(s);
|
|
249
|
+
if (centuryRangeMatch)
|
|
250
|
+
return centuryRangeMatch;
|
|
251
|
+
const century = tryCentury(s);
|
|
252
|
+
if (century)
|
|
253
|
+
return century;
|
|
254
|
+
const decade = tryDecade(s);
|
|
255
|
+
if (decade)
|
|
256
|
+
return decade;
|
|
257
|
+
const single = trySingleYear(s);
|
|
258
|
+
if (single)
|
|
259
|
+
return single;
|
|
260
|
+
const dynasty = tryDynasty(s);
|
|
261
|
+
if (dynasty)
|
|
262
|
+
return dynasty;
|
|
263
|
+
return { yearStart: null, yearEnd: null };
|
|
264
|
+
}
|
|
265
|
+
//# sourceMappingURL=dateParser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dateParser.js","sourceRoot":"","sources":["../src/dateParser.ts"],"names":[],"mappings":"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"}
|
package/dist/db.d.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { Artwork } from './types.js';
|
|
2
|
+
export interface CacheConfig {
|
|
3
|
+
path: string;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* SQLite-backed cache for normalized artworks and search-result IDs.
|
|
7
|
+
*
|
|
8
|
+
* TTL contract:
|
|
9
|
+
* - `objects` rows live for {@link OBJECT_TTL_DAYS} days. Artworks don't
|
|
10
|
+
* change once accessioned, so the long TTL keeps cold-cache HTTP cost low.
|
|
11
|
+
* - `query_cache` rows live for {@link QUERY_TTL_DAYS} days. Museums add
|
|
12
|
+
* new open-access objects regularly, so queries refresh on a shorter cycle.
|
|
13
|
+
*
|
|
14
|
+
* Stale rows are pruned on construction (cheap bulk DELETE) so a long-lived
|
|
15
|
+
* client doesn't accumulate dead rows indefinitely. Reads also treat expired
|
|
16
|
+
* rows as misses; the next write overwrites the row via ON CONFLICT.
|
|
17
|
+
*
|
|
18
|
+
* The DB file is created with mode 0o600. Parameterized statements only —
|
|
19
|
+
* no string-concatenated SQL paths.
|
|
20
|
+
*/
|
|
21
|
+
export declare class Cache {
|
|
22
|
+
private db;
|
|
23
|
+
constructor(config: CacheConfig);
|
|
24
|
+
private init;
|
|
25
|
+
upsertObject(art: Artwork): void;
|
|
26
|
+
getObject(id: string): Artwork | null;
|
|
27
|
+
putQuery(cacheKey: string, ids: string[]): void;
|
|
28
|
+
getQuery(cacheKey: string): string[] | null;
|
|
29
|
+
pruneExpired(): {
|
|
30
|
+
objects: number;
|
|
31
|
+
queries: number;
|
|
32
|
+
};
|
|
33
|
+
private isExpired;
|
|
34
|
+
close(): void;
|
|
35
|
+
}
|
package/dist/db.js
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import Database from 'better-sqlite3';
|
|
2
|
+
import { chmodSync, mkdirSync, existsSync } from 'node:fs';
|
|
3
|
+
import { dirname } from 'node:path';
|
|
4
|
+
const OBJECT_TTL_DAYS = 90;
|
|
5
|
+
const QUERY_TTL_DAYS = 14;
|
|
6
|
+
const MS_PER_DAY = 24 * 60 * 60 * 1000;
|
|
7
|
+
/**
|
|
8
|
+
* SQLite-backed cache for normalized artworks and search-result IDs.
|
|
9
|
+
*
|
|
10
|
+
* TTL contract:
|
|
11
|
+
* - `objects` rows live for {@link OBJECT_TTL_DAYS} days. Artworks don't
|
|
12
|
+
* change once accessioned, so the long TTL keeps cold-cache HTTP cost low.
|
|
13
|
+
* - `query_cache` rows live for {@link QUERY_TTL_DAYS} days. Museums add
|
|
14
|
+
* new open-access objects regularly, so queries refresh on a shorter cycle.
|
|
15
|
+
*
|
|
16
|
+
* Stale rows are pruned on construction (cheap bulk DELETE) so a long-lived
|
|
17
|
+
* client doesn't accumulate dead rows indefinitely. Reads also treat expired
|
|
18
|
+
* rows as misses; the next write overwrites the row via ON CONFLICT.
|
|
19
|
+
*
|
|
20
|
+
* The DB file is created with mode 0o600. Parameterized statements only —
|
|
21
|
+
* no string-concatenated SQL paths.
|
|
22
|
+
*/
|
|
23
|
+
export class Cache {
|
|
24
|
+
db;
|
|
25
|
+
constructor(config) {
|
|
26
|
+
if (!existsSync(dirname(config.path))) {
|
|
27
|
+
mkdirSync(dirname(config.path), { recursive: true, mode: 0o700 });
|
|
28
|
+
}
|
|
29
|
+
const fileExisted = existsSync(config.path);
|
|
30
|
+
this.db = new Database(config.path);
|
|
31
|
+
if (!fileExisted) {
|
|
32
|
+
// Tighten file mode immediately on creation so the cache (which can
|
|
33
|
+
// include rights metadata, snapshots of museum responses, etc.) isn't
|
|
34
|
+
// group/world-readable under loose umasks.
|
|
35
|
+
try {
|
|
36
|
+
chmodSync(config.path, 0o600);
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
// Best-effort; non-fatal on filesystems that don't support chmod.
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
this.db.pragma('journal_mode = WAL');
|
|
43
|
+
this.init();
|
|
44
|
+
this.pruneExpired();
|
|
45
|
+
}
|
|
46
|
+
init() {
|
|
47
|
+
this.db.exec(`
|
|
48
|
+
CREATE TABLE IF NOT EXISTS objects (
|
|
49
|
+
id TEXT PRIMARY KEY,
|
|
50
|
+
museum_code TEXT NOT NULL,
|
|
51
|
+
title TEXT NOT NULL,
|
|
52
|
+
artist_name TEXT,
|
|
53
|
+
attribution_type TEXT NOT NULL,
|
|
54
|
+
display_date TEXT,
|
|
55
|
+
year_start INTEGER,
|
|
56
|
+
year_end INTEGER,
|
|
57
|
+
medium TEXT,
|
|
58
|
+
region TEXT,
|
|
59
|
+
period TEXT,
|
|
60
|
+
license_type TEXT NOT NULL,
|
|
61
|
+
full_record TEXT NOT NULL,
|
|
62
|
+
cached_at TEXT NOT NULL
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
CREATE INDEX IF NOT EXISTS idx_objects_year_start ON objects(year_start);
|
|
66
|
+
CREATE INDEX IF NOT EXISTS idx_objects_year_end ON objects(year_end);
|
|
67
|
+
CREATE INDEX IF NOT EXISTS idx_objects_region ON objects(region);
|
|
68
|
+
CREATE INDEX IF NOT EXISTS idx_objects_period ON objects(period);
|
|
69
|
+
CREATE INDEX IF NOT EXISTS idx_objects_artist ON objects(artist_name);
|
|
70
|
+
CREATE INDEX IF NOT EXISTS idx_objects_museum ON objects(museum_code);
|
|
71
|
+
|
|
72
|
+
CREATE TABLE IF NOT EXISTS query_cache (
|
|
73
|
+
cache_key TEXT PRIMARY KEY,
|
|
74
|
+
ids_json TEXT NOT NULL,
|
|
75
|
+
cached_at TEXT NOT NULL
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
-- Forward-compat scaffold for v1.0 artist obscurity scoring across the
|
|
79
|
+
-- federated corpus. Not yet populated; do not depend on these columns.
|
|
80
|
+
CREATE TABLE IF NOT EXISTS artists (
|
|
81
|
+
name TEXT PRIMARY KEY,
|
|
82
|
+
object_count INTEGER NOT NULL DEFAULT 0,
|
|
83
|
+
museum_count INTEGER NOT NULL DEFAULT 0,
|
|
84
|
+
museums_json TEXT NOT NULL DEFAULT '[]'
|
|
85
|
+
);
|
|
86
|
+
`);
|
|
87
|
+
}
|
|
88
|
+
upsertObject(art) {
|
|
89
|
+
const stmt = this.db.prepare(`
|
|
90
|
+
INSERT INTO objects (
|
|
91
|
+
id, museum_code, title, artist_name, attribution_type,
|
|
92
|
+
display_date, year_start, year_end, medium, region, period,
|
|
93
|
+
license_type, full_record, cached_at
|
|
94
|
+
) VALUES (
|
|
95
|
+
@id, @museum_code, @title, @artist_name, @attribution_type,
|
|
96
|
+
@display_date, @year_start, @year_end, @medium, @region, @period,
|
|
97
|
+
@license_type, @full_record, @cached_at
|
|
98
|
+
)
|
|
99
|
+
ON CONFLICT(id) DO UPDATE SET
|
|
100
|
+
title = excluded.title,
|
|
101
|
+
artist_name = excluded.artist_name,
|
|
102
|
+
attribution_type = excluded.attribution_type,
|
|
103
|
+
display_date = excluded.display_date,
|
|
104
|
+
year_start = excluded.year_start,
|
|
105
|
+
year_end = excluded.year_end,
|
|
106
|
+
medium = excluded.medium,
|
|
107
|
+
region = excluded.region,
|
|
108
|
+
period = excluded.period,
|
|
109
|
+
license_type = excluded.license_type,
|
|
110
|
+
full_record = excluded.full_record,
|
|
111
|
+
cached_at = excluded.cached_at
|
|
112
|
+
`);
|
|
113
|
+
stmt.run({
|
|
114
|
+
id: art.id,
|
|
115
|
+
museum_code: art.museum.code,
|
|
116
|
+
title: art.title,
|
|
117
|
+
artist_name: art.artist.name,
|
|
118
|
+
attribution_type: art.artist.attributionType,
|
|
119
|
+
display_date: art.displayDate,
|
|
120
|
+
year_start: art.yearStart,
|
|
121
|
+
year_end: art.yearEnd,
|
|
122
|
+
medium: art.medium,
|
|
123
|
+
region: art.region,
|
|
124
|
+
period: art.period,
|
|
125
|
+
license_type: art.license.type,
|
|
126
|
+
full_record: JSON.stringify(art),
|
|
127
|
+
cached_at: new Date().toISOString(),
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
getObject(id) {
|
|
131
|
+
const row = this.db.prepare(`SELECT full_record, cached_at FROM objects WHERE id = ?`).get(id);
|
|
132
|
+
if (!row)
|
|
133
|
+
return null;
|
|
134
|
+
if (this.isExpired(row.cached_at, OBJECT_TTL_DAYS))
|
|
135
|
+
return null;
|
|
136
|
+
try {
|
|
137
|
+
return JSON.parse(row.full_record);
|
|
138
|
+
}
|
|
139
|
+
catch {
|
|
140
|
+
// A malformed row (manual DB tinkering, OS-level corruption, schema
|
|
141
|
+
// drift from a prior version) shouldn't poison the request — treat it
|
|
142
|
+
// as a cache miss; the next write will overwrite the row.
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
putQuery(cacheKey, ids) {
|
|
147
|
+
this.db
|
|
148
|
+
.prepare(`INSERT INTO query_cache (cache_key, ids_json, cached_at)
|
|
149
|
+
VALUES (?, ?, ?)
|
|
150
|
+
ON CONFLICT(cache_key) DO UPDATE SET ids_json = excluded.ids_json, cached_at = excluded.cached_at`)
|
|
151
|
+
.run(cacheKey, JSON.stringify(ids), new Date().toISOString());
|
|
152
|
+
}
|
|
153
|
+
getQuery(cacheKey) {
|
|
154
|
+
const row = this.db.prepare(`SELECT ids_json, cached_at FROM query_cache WHERE cache_key = ?`).get(cacheKey);
|
|
155
|
+
if (!row)
|
|
156
|
+
return null;
|
|
157
|
+
if (this.isExpired(row.cached_at, QUERY_TTL_DAYS))
|
|
158
|
+
return null;
|
|
159
|
+
try {
|
|
160
|
+
return JSON.parse(row.ids_json);
|
|
161
|
+
}
|
|
162
|
+
catch {
|
|
163
|
+
return null;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
pruneExpired() {
|
|
167
|
+
const objectsCutoff = new Date(Date.now() - OBJECT_TTL_DAYS * MS_PER_DAY).toISOString();
|
|
168
|
+
const queriesCutoff = new Date(Date.now() - QUERY_TTL_DAYS * MS_PER_DAY).toISOString();
|
|
169
|
+
const objects = this.db.prepare(`DELETE FROM objects WHERE cached_at < ?`).run(objectsCutoff);
|
|
170
|
+
const queries = this.db.prepare(`DELETE FROM query_cache WHERE cached_at < ?`).run(queriesCutoff);
|
|
171
|
+
return { objects: objects.changes, queries: queries.changes };
|
|
172
|
+
}
|
|
173
|
+
isExpired(isoTimestamp, ttlDays) {
|
|
174
|
+
const cachedAt = new Date(isoTimestamp).getTime();
|
|
175
|
+
const expiresAt = cachedAt + ttlDays * MS_PER_DAY;
|
|
176
|
+
return Date.now() > expiresAt;
|
|
177
|
+
}
|
|
178
|
+
close() {
|
|
179
|
+
this.db.close();
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
//# sourceMappingURL=db.js.map
|
package/dist/db.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"db.js","sourceRoot":"","sources":["../src/db.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { parseDisplayDate } from '../dateParser.js';
|
|
2
|
+
import { validateMetLicense } from '../licenseGate.js';
|
|
3
|
+
import { cleanArtistName, detectAttributionType, normalizeRegion } from '../mappings.js';
|
|
4
|
+
const MET_API = 'https://collectionapi.metmuseum.org/public/collection/v1';
|
|
5
|
+
function asString(v) {
|
|
6
|
+
return typeof v === 'string' ? v : '';
|
|
7
|
+
}
|
|
8
|
+
function asOptionalString(v) {
|
|
9
|
+
return typeof v === 'string' && v.length > 0 ? v : undefined;
|
|
10
|
+
}
|
|
11
|
+
export const metFetcher = {
|
|
12
|
+
code: 'met',
|
|
13
|
+
name: 'The Metropolitan Museum of Art',
|
|
14
|
+
async search(query, limit, options = {}) {
|
|
15
|
+
const url = new URL(`${MET_API}/search`);
|
|
16
|
+
url.searchParams.set('q', query);
|
|
17
|
+
if (options.hasImage !== false) {
|
|
18
|
+
url.searchParams.set('hasImages', 'true');
|
|
19
|
+
}
|
|
20
|
+
url.searchParams.set('isPublicDomain', 'true');
|
|
21
|
+
const res = await fetch(url);
|
|
22
|
+
if (!res.ok)
|
|
23
|
+
throw new Error(`Met search failed: ${res.status}`);
|
|
24
|
+
const json = (await res.json());
|
|
25
|
+
const ids = json.objectIDs ?? [];
|
|
26
|
+
return ids.slice(0, limit).map((n) => `met:${n}`);
|
|
27
|
+
},
|
|
28
|
+
async getRaw(id) {
|
|
29
|
+
const numeric = id.replace(/^met:/, '');
|
|
30
|
+
const res = await fetch(`${MET_API}/objects/${numeric}`);
|
|
31
|
+
if (!res.ok)
|
|
32
|
+
throw new Error(`Met get failed for ${id}: ${res.status}`);
|
|
33
|
+
return res.json();
|
|
34
|
+
},
|
|
35
|
+
normalize(raw) {
|
|
36
|
+
if (!raw || typeof raw !== 'object') {
|
|
37
|
+
return {
|
|
38
|
+
status: 'rejected',
|
|
39
|
+
rejection: {
|
|
40
|
+
id: 'met:unknown',
|
|
41
|
+
museumCode: 'met',
|
|
42
|
+
reason: 'response not an object',
|
|
43
|
+
rawSnapshot: raw,
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
const r = raw;
|
|
48
|
+
const objectID = r.objectID;
|
|
49
|
+
// Met IDs are always positive integers. Anything else flunks ID_REGEX
|
|
50
|
+
// downstream; emit a placeholder so the rejection still carries a stable
|
|
51
|
+
// (and obviously bogus) id for logs.
|
|
52
|
+
const validId = typeof objectID === 'number' && Number.isInteger(objectID) && objectID > 0;
|
|
53
|
+
const id = validId ? `met:${objectID}` : 'met:unknown';
|
|
54
|
+
const decision = validateMetLicense(raw);
|
|
55
|
+
if (!decision.accepted || !decision.license) {
|
|
56
|
+
return {
|
|
57
|
+
status: 'rejected',
|
|
58
|
+
rejection: {
|
|
59
|
+
id,
|
|
60
|
+
museumCode: 'met',
|
|
61
|
+
reason: decision.reason,
|
|
62
|
+
rawSnapshot: raw,
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
if (!validId) {
|
|
67
|
+
return {
|
|
68
|
+
status: 'rejected',
|
|
69
|
+
rejection: {
|
|
70
|
+
id,
|
|
71
|
+
museumCode: 'met',
|
|
72
|
+
reason: 'met: missing or non-integer objectID',
|
|
73
|
+
rawSnapshot: raw,
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
const displayDate = asString(r.objectDate);
|
|
78
|
+
const dateRange = parseDisplayDate(displayDate);
|
|
79
|
+
const artistRaw = asString(r.artistDisplayName);
|
|
80
|
+
const attributionType = detectAttributionType(artistRaw);
|
|
81
|
+
const cleanName = cleanArtistName(artistRaw);
|
|
82
|
+
const cultureOrCountry = asString(r.culture) || asString(r.country) || asString(r.classification);
|
|
83
|
+
const region = normalizeRegion(cultureOrCountry);
|
|
84
|
+
const periodRaw = (asString(r.period) || asString(r.dynasty)).toLowerCase().trim();
|
|
85
|
+
const period = periodRaw.replace(/\s*\([^)]*\)\s*$/, '').trim() || null;
|
|
86
|
+
const fullImage = asString(r.primaryImage) || asString(r.primaryImageSmall);
|
|
87
|
+
const thumbnail = asOptionalString(r.primaryImageSmall);
|
|
88
|
+
const beginDate = asString(r.artistBeginDate);
|
|
89
|
+
const endDate = asString(r.artistEndDate);
|
|
90
|
+
const lifespan = beginDate || endDate ? `${beginDate}–${endDate}`.replace(/^–|–$/g, '') : undefined;
|
|
91
|
+
const artwork = {
|
|
92
|
+
id,
|
|
93
|
+
museum: {
|
|
94
|
+
code: 'met',
|
|
95
|
+
name: 'The Metropolitan Museum of Art',
|
|
96
|
+
url: 'https://www.metmuseum.org',
|
|
97
|
+
},
|
|
98
|
+
title: (asString(r.title) || '(Untitled)').trim(),
|
|
99
|
+
artist: {
|
|
100
|
+
name: cleanName || 'Unknown',
|
|
101
|
+
nationality: asOptionalString(r.artistNationality),
|
|
102
|
+
lifespan,
|
|
103
|
+
attributionType,
|
|
104
|
+
},
|
|
105
|
+
displayDate,
|
|
106
|
+
yearStart: dateRange.yearStart,
|
|
107
|
+
yearEnd: dateRange.yearEnd,
|
|
108
|
+
medium: asString(r.medium),
|
|
109
|
+
region,
|
|
110
|
+
period,
|
|
111
|
+
imageUrls: {
|
|
112
|
+
full: fullImage,
|
|
113
|
+
thumbnail,
|
|
114
|
+
},
|
|
115
|
+
imageOpenAccess: decision.imageOpenAccess,
|
|
116
|
+
metadataOpenAccess: decision.metadataOpenAccess,
|
|
117
|
+
license: decision.license,
|
|
118
|
+
source: {
|
|
119
|
+
apiUrl: `${MET_API}/objects/${objectID}`,
|
|
120
|
+
pageUrl: asString(r.objectURL) || `https://www.metmuseum.org/art/collection/search/${objectID}`,
|
|
121
|
+
},
|
|
122
|
+
description: asOptionalString(r.objectName),
|
|
123
|
+
rawTags: Array.isArray(r.tags)
|
|
124
|
+
? r.tags
|
|
125
|
+
.map((t) => (t && typeof t === 'object' ? t.term : undefined))
|
|
126
|
+
.filter((s) => typeof s === 'string')
|
|
127
|
+
: undefined,
|
|
128
|
+
};
|
|
129
|
+
return { status: 'accepted', artwork };
|
|
130
|
+
},
|
|
131
|
+
};
|
|
132
|
+
//# sourceMappingURL=met.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"met.js","sourceRoot":"","sources":["../../src/fetchers/met.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AACvD,OAAO,EAAE,eAAe,EAAE,qBAAqB,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAIzF,MAAM,OAAO,GAAG,0DAA0D,CAAC;AAE3E,SAAS,QAAQ,CAAC,CAAU;IAC1B,OAAO,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;AACxC,CAAC;AAED,SAAS,gBAAgB,CAAC,CAAU;IAClC,OAAO,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;AAC/D,CAAC;AAED,MAAM,CAAC,MAAM,UAAU,GAAY;IACjC,IAAI,EAAE,KAAK;IACX,IAAI,EAAE,gCAAgC;IAEtC,KAAK,CAAC,MAAM,CAAC,KAAa,EAAE,KAAa,EAAE,UAAyB,EAAE;QACpE,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,GAAG,OAAO,SAAS,CAAC,CAAC;QACzC,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;QACjC,IAAI,OAAO,CAAC,QAAQ,KAAK,KAAK,EAAE,CAAC;YAC/B,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC;QAC5C,CAAC;QACD,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,gBAAgB,EAAE,MAAM,CAAC,CAAC;QAE/C,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,CAAC,CAAC;QAC7B,IAAI,CAAC,GAAG,CAAC,EAAE;YAAE,MAAM,IAAI,KAAK,CAAC,sBAAsB,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC;QACjE,MAAM,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAoD,CAAC;QACnF,MAAM,GAAG,GAAG,IAAI,CAAC,SAAS,IAAI,EAAE,CAAC;QACjC,OAAO,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;IACpD,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,EAAU;QACrB,MAAM,OAAO,GAAG,EAAE,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;QACxC,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,OAAO,YAAY,OAAO,EAAE,CAAC,CAAC;QACzD,IAAI,CAAC,GAAG,CAAC,EAAE;YAAE,MAAM,IAAI,KAAK,CAAC,sBAAsB,EAAE,KAAK,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC;QACxE,OAAO,GAAG,CAAC,IAAI,EAAE,CAAC;IACpB,CAAC;IAED,SAAS,CAAC,GAAY;QACpB,IAAI,CAAC,GAAG,IAAI,OAAO,GAAG,KAAK,QAAQ,EAAE,CAAC;YACpC,OAAO;gBACL,MAAM,EAAE,UAAU;gBAClB,SAAS,EAAE;oBACT,EAAE,EAAE,aAAa;oBACjB,UAAU,EAAE,KAAK;oBACjB,MAAM,EAAE,wBAAwB;oBAChC,WAAW,EAAE,GAAG;iBACjB;aACF,CAAC;QACJ,CAAC;QACD,MAAM,CAAC,GAAG,GAA8B,CAAC;QACzC,MAAM,QAAQ,GAAG,CAAC,CAAC,QAAQ,CAAC;QAC5B,sEAAsE;QACtE,yEAAyE;QACzE,qCAAqC;QACrC,MAAM,OAAO,GAAG,OAAO,QAAQ,KAAK,QAAQ,IAAI,MAAM,CAAC,SAAS,CAAC,QAAQ,CAAC,IAAI,QAAQ,GAAG,CAAC,CAAC;QAC3F,MAAM,EAAE,GAAG,OAAO,CAAC,CAAC,CAAC,OAAO,QAAQ,EAAE,CAAC,CAAC,CAAC,aAAa,CAAC;QAEvD,MAAM,QAAQ,GAAG,kBAAkB,CAAC,GAAG,CAAC,CAAC;QACzC,IAAI,CAAC,QAAQ,CAAC,QAAQ,IAAI,CAAC,QAAQ,CAAC,OAAO,EAAE,CAAC;YAC5C,OAAO;gBACL,MAAM,EAAE,UAAU;gBAClB,SAAS,EAAE;oBACT,EAAE;oBACF,UAAU,EAAE,KAAK;oBACjB,MAAM,EAAE,QAAQ,CAAC,MAAM;oBACvB,WAAW,EAAE,GAAG;iBACjB;aACF,CAAC;QACJ,CAAC;QAED,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,OAAO;gBACL,MAAM,EAAE,UAAU;gBAClB,SAAS,EAAE;oBACT,EAAE;oBACF,UAAU,EAAE,KAAK;oBACjB,MAAM,EAAE,sCAAsC;oBAC9C,WAAW,EAAE,GAAG;iBACjB;aACF,CAAC;QACJ,CAAC;QAED,MAAM,WAAW,GAAG,QAAQ,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC;QAC3C,MAAM,SAAS,GAAG,gBAAgB,CAAC,WAAW,CAAC,CAAC;QAEhD,MAAM,SAAS,GAAG,QAAQ,CAAC,CAAC,CAAC,iBAAiB,CAAC,CAAC;QAChD,MAAM,eAAe,GAAG,qBAAqB,CAAC,SAAS,CAAC,CAAC;QACzD,MAAM,SAAS,GAAG,eAAe,CAAC,SAAS,CAAC,CAAC;QAE7C,MAAM,gBAAgB,GACpB,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,QAAQ,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC;QAC3E,MAAM,MAAM,GAAG,eAAe,CAAC,gBAAgB,CAAC,CAAC;QAEjD,MAAM,SAAS,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,IAAI,EAAE,CAAC;QACnF,MAAM,MAAM,GAAG,SAAS,CAAC,OAAO,CAAC,kBAAkB,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,IAAI,IAAI,CAAC;QAExE,MAAM,SAAS,GAAG,QAAQ,CAAC,CAAC,CAAC,YAAY,CAAC,IAAI,QAAQ,CAAC,CAAC,CAAC,iBAAiB,CAAC,CAAC;QAC5E,MAAM,SAAS,GAAG,gBAAgB,CAAC,CAAC,CAAC,iBAAiB,CAAC,CAAC;QAExD,MAAM,SAAS,GAAG,QAAQ,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC;QAC9C,MAAM,OAAO,GAAG,QAAQ,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC;QAC1C,MAAM,QAAQ,GAAG,SAAS,IAAI,OAAO,CAAC,CAAC,CAAC,GAAG,SAAS,IAAI,OAAO,EAAE,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QAEpG,MAAM,OAAO,GAAY;YACvB,EAAE;YACF,MAAM,EAAE;gBACN,IAAI,EAAE,KAAK;gBACX,IAAI,EAAE,gCAAgC;gBACtC,GAAG,EAAE,2BAA2B;aACjC;YACD,KAAK,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,YAAY,CAAC,CAAC,IAAI,EAAE;YACjD,MAAM,EAAE;gBACN,IAAI,EAAE,SAAS,IAAI,SAAS;gBAC5B,WAAW,EAAE,gBAAgB,CAAC,CAAC,CAAC,iBAAiB,CAAC;gBAClD,QAAQ;gBACR,eAAe;aAChB;YACD,WAAW;YACX,SAAS,EAAE,SAAS,CAAC,SAAS;YAC9B,OAAO,EAAE,SAAS,CAAC,OAAO;YAC1B,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC;YAC1B,MAAM;YACN,MAAM;YACN,SAAS,EAAE;gBACT,IAAI,EAAE,SAAS;gBACf,SAAS;aACV;YACD,eAAe,EAAE,QAAQ,CAAC,eAAe;YACzC,kBAAkB,EAAE,QAAQ,CAAC,kBAAkB;YAC/C,OAAO,EAAE,QAAQ,CAAC,OAAO;YACzB,MAAM,EAAE;gBACN,MAAM,EAAE,GAAG,OAAO,YAAY,QAAQ,EAAE;gBACxC,OAAO,EAAE,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC,IAAI,mDAAmD,QAAQ,EAAE;aAChG;YACD,WAAW,EAAE,gBAAgB,CAAC,CAAC,CAAC,UAAU,CAAC;YAC3C,OAAO,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC;gBAC5B,CAAC,CAAE,CAAC,CAAC,IAAuB;qBACvB,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAE,CAAwB,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;qBACrF,MAAM,CAAC,CAAC,CAAC,EAAe,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC;gBACtD,CAAC,CAAC,SAAS;SACd,CAAC;QAEF,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,OAAO,EAAE,CAAC;IACzC,CAAC;CACF,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { ValidationResult } from '../types.js';
|
|
2
|
+
export interface SearchOptions {
|
|
3
|
+
hasImage?: boolean;
|
|
4
|
+
}
|
|
5
|
+
export interface Fetcher {
|
|
6
|
+
code: string;
|
|
7
|
+
name: string;
|
|
8
|
+
search(query: string, limit: number, options?: SearchOptions): Promise<string[]>;
|
|
9
|
+
getRaw(id: string): Promise<unknown>;
|
|
10
|
+
normalize(raw: unknown): ValidationResult;
|
|
11
|
+
}
|