mcp-databnf 2.1.0 → 2.1.2
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/CHANGELOG.md +18 -0
- package/README.md +4 -4
- package/dist/bnf/client.js +1 -1
- package/dist/bnf/client.js.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/server.json +4 -4
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/server.ts","../src/config.ts","../src/version.ts","../src/errors.ts","../src/bnf/cache.ts","../src/bnf/endpoint.ts","../src/bnf/http.ts","../src/bnf/sparql.ts","../src/bnf/queries.ts","../src/bnf/parse.ts","../src/bnf/rateLimiter.ts","../src/bnf/client.ts","../src/tools/findDigitised.ts","../src/tools/arguments.ts","../src/tools/shared.ts","../src/tools/getAuthor.ts","../src/tools/getWork.ts","../src/tools/listEditions.ts","../src/tools/listWorks.ts","../src/tools/searchAuthors.ts","../src/tools/searchWorks.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n * Entry point.\n *\n * stdout carries the protocol and nothing else: every diagnostic goes to\n * stderr, because a stray line on stdout corrupts the session for the client.\n */\n\nimport process from \"node:process\";\nimport { StdioServerTransport } from \"@modelcontextprotocol/sdk/server/stdio.js\";\nimport { createServer } from \"./server.js\";\n\nasync function main(): Promise<void> {\n const server = createServer();\n const transport = new StdioServerTransport();\n await server.connect(transport);\n\n const shutdown = async () => {\n await server.close().catch(() => undefined);\n process.exit(0);\n };\n process.on(\"SIGINT\", shutdown);\n process.on(\"SIGTERM\", shutdown);\n}\n\nmain().catch((error: unknown) => {\n const message = error instanceof Error ? error.message : String(error);\n process.stderr.write(`[mcp-databnf] fatal: ${message}\\n`);\n process.exit(1);\n});\n","/**\n * Wiring: one client, seven tools, and the guidance a model reads before using\n * any of them.\n */\n\nimport { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { BnfClient } from \"./bnf/client.js\";\nimport type { Config, Logger } from \"./config.js\";\nimport { createLogger, loadConfig } from \"./config.js\";\nimport {\n findDigitisedDescription,\n findDigitisedInput,\n findDigitisedOutput,\n runFindDigitised,\n} from \"./tools/findDigitised.js\";\nimport type { FindDigitisedArgs } from \"./tools/findDigitised.js\";\nimport {\n getAuthorDescription,\n getAuthorInput,\n getAuthorOutput,\n runGetAuthor,\n} from \"./tools/getAuthor.js\";\nimport type { GetAuthorArgs } from \"./tools/getAuthor.js\";\nimport { getWorkDescription, getWorkInput, getWorkOutput, runGetWork } from \"./tools/getWork.js\";\nimport type { GetWorkArgs } from \"./tools/getWork.js\";\nimport {\n listEditionsDescription,\n listEditionsInput,\n listEditionsOutput,\n runListEditions,\n} from \"./tools/listEditions.js\";\nimport type { ListEditionsArgs } from \"./tools/listEditions.js\";\nimport {\n listWorksDescription,\n listWorksInput,\n listWorksOutput,\n runListWorks,\n} from \"./tools/listWorks.js\";\nimport type { ListWorksArgs } from \"./tools/listWorks.js\";\nimport {\n runSearchAuthors,\n searchAuthorsDescription,\n searchAuthorsInput,\n searchAuthorsOutput,\n} from \"./tools/searchAuthors.js\";\nimport type { SearchAuthorsArgs } from \"./tools/searchAuthors.js\";\nimport {\n runSearchWorks,\n searchWorksDescription,\n searchWorksInput,\n searchWorksOutput,\n} from \"./tools/searchWorks.js\";\nimport type { SearchWorksArgs } from \"./tools/searchWorks.js\";\nimport { PKG_VERSION } from \"./version.js\";\n\nexport interface CreateServerOptions {\n config?: Config;\n logger?: Logger;\n fetchImpl?: typeof fetch;\n}\n\n/** Nothing here writes or deletes; every tool only reads. */\nconst READ_ONLY = {\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: true,\n} as const;\n\nexport const INSTRUCTIONS = [\n \"Tools for data.bnf.fr, the open catalogue of the Bibliothèque nationale de France. No API key and no account are needed.\",\n \"The usual path is search_authors or search_works to get an identifier, then get_author, get_work or list_editions to read the record it names. What a person wrote is list_works, which walks from a person to the works the catalogue names them the creator of; a title search cannot answer that, because it reads titles and a person's name in a title belongs to the books written about them.\",\n \"A list of a person's works is one link the catalogue holds and not a bibliography: the catalogue credits a person on a record in other ways, and the BnF holds editions whose work it has never established as a record of its own, so a work missing from the list is not a work the person did not write. The catalogue states a work's form as a code from a vocabulary it publishes no label for, and it states no form at all on many works, so keeping the rows carrying one code finds the works that declare it and never all the works of that form.\",\n \"The BnF publishes these metadata on one condition: name the source and state the date they were retrieved. Every answer carries 'retrieved_at' for that reason, and repeating both alongside what you show is what the licence asks for.\",\n \"Both searches read a full-text index that answers whether a title or a name matches and never scores how well, so rows come back ordered by the address of the record. Never present the first row as the best answer, and never describe a count as a ranking. Every listing here is paged along that same order, so a page and the page after it hold different records and nothing between them is skipped.\",\n \"One person can hold several authority records, and search_authors returns all of them: show the caller the choice rather than picking one.\",\n \"A work record is either established, with an ARK of its own, or provisional, addressed under 'temp-work' while a cataloguer settles it. A provisional identifier can change, so prefer an established one when citing.\",\n \"The catalogue describes what the BnF holds; it does not hold the texts. A record can point at a digitised copy on Gallica, and those links are returned as addresses for a person to open. This server never requests gallica.bnf.fr, because the BnF places its metadata and its digitised contents under two different regimes, so it can say a document exists at a link and nothing about what is there.\",\n \"This server paces itself, and a rate_limited error means the endpoint was asked to slow down, never that the thing you asked for is missing.\",\n \"Every result carries a source_url. Credit data.bnf.fr and link what you use.\",\n].join(\" \");\n\nexport function createServer(options: CreateServerOptions = {}): McpServer {\n const config = options.config ?? loadConfig();\n const logger = options.logger ?? createLogger(config.logLevel);\n const client = new BnfClient({\n config,\n logger,\n ...(options.fetchImpl ? { fetchImpl: options.fetchImpl } : {}),\n });\n\n const server = new McpServer(\n { name: \"mcp-databnf\", version: PKG_VERSION },\n { instructions: INSTRUCTIONS },\n );\n\n server.registerTool(\n \"search_authors\",\n {\n title: \"Find a person in the BnF authority file\",\n description: searchAuthorsDescription,\n inputSchema: searchAuthorsInput,\n outputSchema: searchAuthorsOutput,\n annotations: READ_ONLY,\n },\n async (args) => runSearchAuthors(client, args as SearchAuthorsArgs),\n );\n\n server.registerTool(\n \"get_author\",\n {\n title: \"Read a person's record\",\n description: getAuthorDescription,\n inputSchema: getAuthorInput,\n outputSchema: getAuthorOutput,\n annotations: READ_ONLY,\n },\n async (args) => runGetAuthor(client, args as GetAuthorArgs),\n );\n\n server.registerTool(\n \"search_works\",\n {\n title: \"Find a work by its title\",\n description: searchWorksDescription,\n inputSchema: searchWorksInput,\n outputSchema: searchWorksOutput,\n annotations: READ_ONLY,\n },\n async (args) => runSearchWorks(client, args as SearchWorksArgs),\n );\n\n server.registerTool(\n \"get_work\",\n {\n title: \"Read a work's record\",\n description: getWorkDescription,\n inputSchema: getWorkInput,\n outputSchema: getWorkOutput,\n annotations: READ_ONLY,\n },\n async (args) => runGetWork(client, args as GetWorkArgs),\n );\n\n server.registerTool(\n \"list_editions\",\n {\n title: \"List the editions of a work\",\n description: listEditionsDescription,\n inputSchema: listEditionsInput,\n outputSchema: listEditionsOutput,\n annotations: READ_ONLY,\n },\n async (args) => runListEditions(client, args as ListEditionsArgs),\n );\n\n server.registerTool(\n \"list_works\",\n {\n title: \"List the works a person is credited with\",\n description: listWorksDescription,\n inputSchema: listWorksInput,\n outputSchema: listWorksOutput,\n annotations: READ_ONLY,\n },\n async (args) => runListWorks(client, args as ListWorksArgs),\n );\n\n server.registerTool(\n \"find_digitised\",\n {\n title: \"Gather the digitised documents attached to a record\",\n description: findDigitisedDescription,\n inputSchema: findDigitisedInput,\n outputSchema: findDigitisedOutput,\n annotations: READ_ONLY,\n },\n async (args) => runFindDigitised(client, args as FindDigitisedArgs),\n );\n\n logger.info(\n `ready: user-agent=\"${config.userAgent}\", ${config.minIntervalMs}ms between requests, cache ${config.cacheTtlMs}ms`,\n );\n\n return server;\n}\n","/**\n * Settings, read from the environment.\n *\n * A value that cannot be read warns and falls back rather than stopping the\n * server: a typo in one variable should not take away every tool. Warnings go\n * to stderr, because stdout carries the protocol and anything written there\n * corrupts the session.\n */\n\nimport process from \"node:process\";\nimport { PKG_VERSION, REPO_URL } from \"./version.js\";\n\nexport const LOG_LEVELS = [\"silent\", \"error\", \"info\", \"debug\"] as const;\nexport type LogLevel = (typeof LOG_LEVELS)[number];\n\n/**\n * The narrowest spacing this client will ever use, in milliseconds.\n *\n * The BnF publishes no rate for the SPARQL endpoint. It publishes\n * `Crawl-delay: 5` for gallica.bnf.fr and enforces it there by banning an\n * address, so five seconds is the only figure the institution has stated about\n * how fast it wants to be read. Three seconds is the floor here because a\n * SPARQL query is one request where a crawl is thousands, and because every\n * answer is cached: the spacing that matters is the one between two distinct\n * questions.\n *\n * Configuration can slow the server down. It cannot take the spacing below this\n * floor, whether the setting arrives through the environment or through a\n * configuration object handed to the published client.\n */\nexport const MIN_ALLOWED_INTERVAL_MS = 3000;\n/** Beyond this a request would look hung rather than paced. */\nexport const MAX_ALLOWED_INTERVAL_MS = 120_000;\n/** The spacing in force when nothing asks for more. */\nexport const DEFAULT_INTERVAL_MS = 3000;\n\n/**\n * The deadline one query is given.\n *\n * Measured against the live endpoint, a lookup by identifier answers in well\n * under a second, while a full-text query over titles takes several seconds and\n * a query the planner handles badly is dropped by the server with an empty\n * body. Sixty seconds clears the slow honest case without holding the single\n * request slot for a query that will never answer.\n */\nexport const DEFAULT_TIMEOUT_MS = 60_000;\n\nexport interface Config {\n userAgent: string;\n minIntervalMs: number;\n timeoutMs: number;\n maxRetries: number;\n cacheTtlMs: number;\n cacheMaxEntries: number;\n logLevel: LogLevel;\n}\n\nexport const DEFAULT_USER_AGENT = `mcp-databnf/${PKG_VERSION} (+${REPO_URL})`;\n\nexport interface Logger {\n debug: (message: string) => void;\n info: (message: string) => void;\n warn: (message: string) => void;\n error: (message: string) => void;\n}\n\nexport function createLogger(level: LogLevel): Logger {\n const rank = LOG_LEVELS.indexOf(level);\n const write = (at: LogLevel, message: string) => {\n if (rank === 0 || LOG_LEVELS.indexOf(at) > rank) {\n return;\n }\n process.stderr.write(`[mcp-databnf] ${at}: ${message}\\n`);\n };\n return {\n debug: (m) => write(\"debug\", m),\n info: (m) => write(\"info\", m),\n // A warning goes out at the error level so it survives the default\n // setting: a caller has to know that rows were dropped.\n warn: (m) => write(\"error\", m),\n error: (m) => write(\"error\", m),\n };\n}\n\nfunction readInteger(\n env: NodeJS.ProcessEnv,\n name: string,\n fallback: number,\n min: number,\n max: number,\n): number {\n const raw = env[name];\n if (raw === undefined || raw.trim() === \"\") {\n return fallback;\n }\n\n const value = Number(raw);\n if (!(Number.isFinite(value) && Number.isInteger(value))) {\n process.stderr.write(\n `[mcp-databnf] error: ${name}=\"${raw}\" is not a whole number; using ${fallback}.\\n`,\n );\n return fallback;\n }\n if (value < min || value > max) {\n // Clamping silently would let a caller believe a setting took effect when\n // the opposite is true, so the refusal is stated and the default stands.\n process.stderr.write(\n `[mcp-databnf] error: ${name}=${value} is outside ${min}..${max}; using ${fallback}.\\n`,\n );\n return fallback;\n }\n return value;\n}\n\nexport function loadConfig(env: NodeJS.ProcessEnv = process.env): Config {\n const level = env.BNF_LOG_LEVEL as LogLevel | undefined;\n const logLevel = level && LOG_LEVELS.includes(level) ? level : \"error\";\n if (level && !LOG_LEVELS.includes(level)) {\n process.stderr.write(\n `[mcp-databnf] error: BNF_LOG_LEVEL=\"${level}\" is not one of ${LOG_LEVELS.join(\", \")}; using error.\\n`,\n );\n }\n\n const custom = env.BNF_USER_AGENT?.trim();\n\n return {\n // A caller who wants to be recognised may say who they are, and the\n // contact address stays attached: the BnF has to be able to reach a human\n // about traffic it did not expect.\n userAgent: custom ? `${custom} ${DEFAULT_USER_AGENT}` : DEFAULT_USER_AGENT,\n minIntervalMs: readInteger(\n env,\n \"BNF_MIN_INTERVAL_MS\",\n DEFAULT_INTERVAL_MS,\n MIN_ALLOWED_INTERVAL_MS,\n MAX_ALLOWED_INTERVAL_MS,\n ),\n timeoutMs: readInteger(env, \"BNF_TIMEOUT_MS\", DEFAULT_TIMEOUT_MS, 1000, 300_000),\n maxRetries: readInteger(env, \"BNF_MAX_RETRIES\", 3, 0, 8),\n cacheTtlMs: readInteger(env, \"BNF_CACHE_TTL_MS\", 900_000, 0, 86_400_000),\n cacheMaxEntries: readInteger(env, \"BNF_CACHE_MAX_ENTRIES\", 200, 1, 5000),\n logLevel,\n };\n}\n","/**\n * Kept in step with the version in package.json. The User-Agent embeds it, so\n * the BnF can tell which build is calling from its own logs.\n */\nexport const PKG_VERSION = \"2.1.0\";\nexport const REPO_URL = \"https://github.com/smeet666/mcp-databnf\";\n","/**\n * One error type, carrying a code the caller can branch on.\n *\n * The distinction that matters most is between \"the BnF describes no such\n * thing\" and \"the question could not be asked\". Collapsing the two lets a model\n * report an absence it never established, which is a false statement about a\n * catalogue rather than a missing feature.\n */\n\nexport type ErrorCode =\n /** data.bnf.fr answered, and describes no such person, work or edition. */\n | \"not_found\"\n /** The arguments cannot produce a query. */\n | \"invalid_input\"\n /** data.bnf.fr asked this client to slow down or refused it for now. */\n | \"rate_limited\"\n /** An answer arrived in a shape this server cannot read. */\n | \"parse_failure\"\n /** The request could not be completed. */\n | \"network_error\"\n /** The request was abandoned before an answer arrived. */\n | \"timeout\";\n\nexport interface ErrorDetails {\n /** What the caller can do about it, when there is something. */\n hint?: string;\n /** The address that produced the failure, for a bug report. */\n url?: string;\n status?: number;\n /** What was raised underneath, kept for the bug report the hint asks for. */\n cause?: unknown;\n}\n\nexport class BnfError extends Error {\n readonly code: ErrorCode;\n readonly details: ErrorDetails;\n\n constructor(code: ErrorCode, message: string, details: ErrorDetails = {}) {\n super(message, details.cause === undefined ? undefined : { cause: details.cause });\n this.name = \"BnfError\";\n this.code = code;\n this.details = details;\n }\n}\n\nexport const notFound = (message: string, details?: ErrorDetails) =>\n new BnfError(\"not_found\", message, details);\n\nexport const invalidInput = (message: string, hint?: string) =>\n new BnfError(\"invalid_input\", message, hint ? { hint } : {});\n\nexport const rateLimited = (message: string, details?: ErrorDetails) =>\n new BnfError(\"rate_limited\", message, {\n hint: \"Wait a moment and ask again. This says nothing about whether the BnF describes what you asked for.\",\n ...details,\n });\n\nexport const parseFailure = (message: string, details?: ErrorDetails) =>\n new BnfError(\"parse_failure\", message, {\n hint: \"data.bnf.fr may have changed how it answers. Please report this at https://github.com/smeet666/mcp-databnf/issues with the arguments you used.\",\n ...details,\n });\n\nexport const networkError = (message: string, details?: ErrorDetails) =>\n new BnfError(\"network_error\", message, details);\n\nexport const timeout = (message: string, details?: ErrorDetails) =>\n new BnfError(\"timeout\", message, details);\n","/**\n * A small in-memory store, keyed by the query that produced the value.\n *\n * It exists to keep a conversation that asks the same thing twice from asking\n * the endpoint twice, which matters here because a conversation about one\n * author walks the same records repeatedly: the person, then the works, then\n * the editions of one work, then back to the person.\n *\n * Entries expire, the store is bounded, and it holds only what was successfully\n * read: storing a response nobody could parse would serve that failure back for\n * the rest of its lifetime.\n *\n * Each entry keeps the moment it was read. The licence asks for the date the\n * metadata was retrieved, and a cached answer was retrieved when it entered the\n * store rather than when it was served, so the stored moment is what travels\n * with it.\n */\n\ninterface Entry<T> {\n value: T;\n expiresAt: number;\n retrievedAt: number;\n}\n\nexport interface Hit<T> {\n value: T;\n retrievedAt: number;\n}\n\nexport class Cache<T> {\n private readonly entries = new Map<string, Entry<T>>();\n\n constructor(\n private readonly ttlMs: number,\n private readonly maxEntries: number,\n ) {}\n\n get(key: string): Hit<T> | undefined {\n const entry = this.entries.get(key);\n if (!entry) {\n return undefined;\n }\n if (entry.expiresAt <= Date.now()) {\n this.entries.delete(key);\n return undefined;\n }\n // Re-inserting marks it as the most recently used, which is what the\n // eviction below reads.\n this.entries.delete(key);\n this.entries.set(key, entry);\n return { value: entry.value, retrievedAt: entry.retrievedAt };\n }\n\n set(key: string, value: T, retrievedAt: number): void {\n // A lifetime of zero turns the store off rather than expiring at once:\n // nothing is written, so nothing has to be checked on the way out.\n if (this.ttlMs <= 0) {\n return;\n }\n this.entries.delete(key);\n this.entries.set(key, { value, expiresAt: Date.now() + this.ttlMs, retrievedAt });\n while (this.entries.size > this.maxEntries) {\n const oldest = this.entries.keys().next();\n if (oldest.done) {\n break;\n }\n this.entries.delete(oldest.value);\n }\n }\n\n get size(): number {\n return this.entries.size;\n }\n}\n","/**\n * Every host, address and vocabulary term this server reads, in one place. An\n * upstream rename is then a one-file change rather than a hunt through the\n * parsers.\n *\n * The robots file at data.bnf.fr withholds one path, the one ending in\n * `cross-documents`, and nothing built here reaches it. The endpoint below is\n * addressed without a trailing slash: with one, the same address serves a\n * JavaScript query editor rather than the query service.\n */\n\nimport { BnfError } from \"../errors.js\";\n\n/** The root a host name may end on, which names the same host without it. */\nconst TRAILING_DOT = /\\.$/;\nconst FRAGMENT = /#.*$/;\nconst HTTP_SCHEME = /^http:/;\n\n/** The one address this server sends a request to. */\nexport const SPARQL_ENDPOINT = \"https://data.bnf.fr/sparql\";\n\n/** The host every request must be addressed to, checked before each call. */\nexport const ALLOWED_HOST = \"data.bnf.fr\";\n\n/**\n * The host this server describes and never requests.\n *\n * The BnF places its metadata and its digitised contents under two different\n * regimes. The metadata served here is free to reuse with its source and its\n * date of retrieval named. The contents on gallica.bnf.fr are not: their terms\n * make use inside an artificial-intelligence project subject to a paid licence\n * outside academic research, and the site refuses ClaudeBot and GPTBot at the\n * server, then bans the calling address after about fifteen requests whatever\n * the pace.\n *\n * So a Gallica address is a piece of metadata here, rendered as a link for a\n * person to open. `assertRequestable` below is what keeps that a rule rather\n * than an intention.\n */\nexport const GALLICA_HOST = \"gallica.bnf.fr\";\n\n/**\n * Refuse to request anything but the query service.\n *\n * This sits between the query builders and `fetch`, so a future route, a\n * redirect followed by hand, or an address read out of a response cannot become\n * a request to a host this server has no licence to read.\n */\nexport function assertRequestable(url: string): void {\n let parsed: URL;\n try {\n parsed = new URL(url);\n } catch (cause) {\n throw refusal(`mcp-databnf refuses to request \"${url}\": it is not an address.`, cause);\n }\n const host = parsed.hostname.toLowerCase().replace(TRAILING_DOT, \"\");\n if (host !== ALLOWED_HOST) {\n throw refusal(\n `mcp-databnf requests ${ALLOWED_HOST} and nothing else; \"${host}\" was refused. ` +\n \"Gallica in particular is described from the catalogue and never read.\",\n );\n }\n}\n\n/**\n * The refusal, raised as an error the transport will not retry.\n *\n * A plain error here would be read as a request that failed to complete, and the\n * next attempt would be made against the same address. A decision not to call a\n * host is settled, so it has to arrive as an answer rather than as a mishap.\n */\nconst refusal = (message: string, cause?: unknown) =>\n new BnfError(\"network_error\", message, {\n hint: \"This server reads the BnF catalogue at data.bnf.fr. It holds no licence to read the digitised contents, so it does not follow an address that leaves that host.\",\n cause,\n });\n\n/** True when an address points at Gallica, whatever the scheme or the case. */\nexport function isGallicaAddress(address: string): boolean {\n try {\n // A trailing dot names the root of the domain tree and resolves to the same\n // host. Comparing without stripping it would read a Gallica address as\n // somewhere else, and the link would be dropped from the answer.\n const host = new URL(address).hostname.toLowerCase().replace(TRAILING_DOT, \"\");\n return host === GALLICA_HOST || host.endsWith(`.${GALLICA_HOST}`);\n } catch {\n return false;\n }\n}\n\n/** Namespaces the queries use, emitted as a PREFIX block. */\nexport const PREFIXES = {\n foaf: \"http://xmlns.com/foaf/0.1/\",\n rdf: \"http://www.w3.org/1999/02/22-rdf-syntax-ns#\",\n rdfs: \"http://www.w3.org/2000/01/rdf-schema#\",\n owl: \"http://www.w3.org/2002/07/owl#\",\n skos: \"http://www.w3.org/2004/02/skos/core#\",\n dcterms: \"http://purl.org/dc/terms/\",\n bio: \"http://vocab.org/bio/0.1/\",\n bnf: \"http://data.bnf.fr/ontology/bnf-onto/\",\n bibo: \"http://purl.org/ontology/bibo/\",\n isni: \"http://isni.org/ontology#\",\n /** RDA group 2 elements, which carry the biographical fields. */\n rdag2: \"http://rdvocab.info/ElementsGr2/\",\n /** RDA elements, which carry the bibliographic fields. */\n rdae: \"http://rdvocab.info/Elements/\",\n /** RDA relationships between work, expression, manifestation and item. */\n rdarel: \"http://rdvocab.info/RDARelationshipsWEMI/\",\n /** The FRBR classes data.bnf.fr types its entities with. */\n rdafrbr: \"http://rdvocab.info/uri/schema/FRBRentitiesRDA/\",\n} as const;\n\nexport const PREFIX_BLOCK = Object.entries(PREFIXES)\n .map(([name, iri]) => `PREFIX ${name}: <${iri}>`)\n .join(\"\\n\");\n\n/** The two shapes an entity address takes in this dataset. */\nexport const ARK_BASE = \"http://data.bnf.fr/ark:/12148/\";\nexport const TEMP_WORK_BASE = \"http://data.bnf.fr/temp-work/\";\n\n/** The page a person can open for a record, which every result carries. */\nexport const publicPageFor = (entityIri: string): string =>\n entityIri.replace(FRAGMENT, \"\").replace(HTTP_SCHEME, \"https:\");\n\n/** The BnF general catalogue, where a record is described for a reader. */\nexport const catalogueUrlFor = (arkId: string): string =>\n `https://catalogue.bnf.fr/ark:/12148/${arkId}`;\n\n/**\n * The licence this dataset is published under, and what it asks for.\n *\n * The BnF states it in one sentence: the metadata may be used freely and at no\n * cost, provided the source is named and the date of retrieval is stated. Both\n * halves are therefore attached to every answer rather than left to a caller.\n */\nexport const LICENCE_FR =\n \"L'utilisation de ces métadonnées est libre et gratuite sous réserve du maintien de la mention de leur source et de l'indication de leur date de récupération.\";\n\nexport const LICENCE_EN =\n \"These metadata may be used freely and at no cost, provided their source is named and the date they were retrieved is stated.\";\n","/**\n * One query sent to the endpoint, with a deadline and bounded retries.\n *\n * Two things separate a retry worth making from one that only adds load. A\n * refusal that carries a time to come back is obeyed rather than guessed at,\n * and an answer the service meant is never retried: asking again for something\n * that is not there wastes a request and delays the honest answer.\n *\n * The query travels in the body of a POST. A long query in a query string is\n * refused by an intermediary before it reaches the service, and a POST keeps the\n * text out of every access log between here and there.\n */\n\nimport {\n invalidInput,\n networkError,\n parseFailure,\n rateLimited,\n timeout as timeoutError,\n} from \"../errors.js\";\nimport type { Logger } from \"../config.js\";\nimport { SPARQL_ENDPOINT, assertRequestable } from \"./endpoint.js\";\nimport type { RateLimiter } from \"./rateLimiter.js\";\n\n/** The shape the endpoint's own engine writes a refusal in. */\nconst VIRTUOSO_ERROR = /^Virtuoso\\s+(\\w+)\\s+Error\\s+([\\s\\S]*)$/;\n\n/** The JSON shape a SPARQL SELECT answers with. */\nexport interface SparqlTerm {\n type: \"uri\" | \"literal\" | \"typed-literal\" | \"bnode\";\n value: string;\n datatype?: string;\n \"xml:lang\"?: string;\n}\n\nexport type SparqlRow = Record<string, SparqlTerm | undefined>;\n\nexport interface SparqlResults {\n head: { vars?: string[] };\n results: { bindings: SparqlRow[] };\n}\n\nexport interface QueryOptions {\n query: string;\n userAgent: string;\n timeoutMs: number;\n maxRetries: number;\n limiter: RateLimiter;\n logger: Logger;\n fetchImpl?: typeof fetch;\n}\n\n/** Statuses worth another attempt: the service is busy, not answering \"no\". */\nconst RETRYABLE = new Set([408, 425, 429, 500, 502, 503, 504]);\n/**\n * Statuses that mean the service is refusing for now and naming a delay.\n *\n * These produce a `rate_limited` error and honour a `Retry-After` header. A\n * server error is a different claim, so it keeps its own code even though it\n * widens the spacing the same way.\n */\nconst PUSH_BACK = new Set([429, 503]);\n\n/**\n * The longest wait worth taking rather than reporting.\n *\n * A refusal may name any delay, and an hour is a legal answer. Sleeping through\n * it holds the one request slot this server has, so every other tool waits\n * behind a call whose caller has long since given up. Past this point the wait\n * is the answer, and the caller decides what to do with it.\n */\nconst LONGEST_WAIT_MS = 30_000;\n\n/**\n * How many times a query that never answered is worth repeating.\n *\n * A query that did not respond within its budget is one the planner is\n * struggling with. Repeating it adds the same load again, and each attempt\n * holds the slot for the full deadline.\n */\nconst RETRIES_AFTER_SILENCE = 1;\n\n/**\n * Redirects worth following before calling it a loop.\n *\n * The query service answers directly, so one hop covers a host that has moved\n * and two cover a scheme change on top of it. Beyond that the answer is a\n * redirect rather than a result.\n */\nconst MAX_REDIRECTS = 2;\n\n/**\n * Read a Retry-After header, which is either a number of seconds or a date.\n * Returns null when it says neither, so the caller falls back to its own wait.\n */\nexport function parseRetryAfter(value: string | null, now = Date.now()): number | null {\n if (!value) {\n return null;\n }\n const trimmed = value.trim();\n\n const seconds = Number(trimmed);\n if (Number.isFinite(seconds) && seconds >= 0) {\n return Math.round(seconds * 1000);\n }\n\n const at = Date.parse(trimmed);\n if (Number.isNaN(at)) {\n return null;\n }\n return Math.max(0, at - now);\n}\n\n/** Growing wait with jitter, so several clients do not return in step. */\nfunction backoffMs(attempt: number): number {\n const base = Math.min(16_000, 1000 * 2 ** attempt);\n return base + Math.floor(Math.random() * 500);\n}\n\n/**\n * What a Virtuoso says when it will not run a query.\n *\n * It answers in plain text rather than in the result format, and the leading\n * code says which half went wrong: a compiler code means the query as written\n * could not be understood, and a runtime code means it was understood and then\n * abandoned, usually because it asked for more of the graph than the service\n * will spend on one caller.\n */\nexport function readEngineError(\n body: string,\n): { kind: \"compile\" | \"runtime\"; text: string } | null {\n const match = VIRTUOSO_ERROR.exec(body.trim());\n if (!match) {\n return null;\n }\n const state = match[1] ?? \"\";\n const text = (match[2] ?? \"\").split(\"\\n\")[0]?.trim() ?? \"\";\n // 37000 is the compiler's own state; the rest come from execution.\n return { kind: state === \"37000\" ? \"compile\" : \"runtime\", text };\n}\n\nasync function postOnce(\n options: QueryOptions,\n signal: AbortSignal,\n): Promise<{ status: number; headers: Headers; body: string; ok: boolean }> {\n assertRequestable(SPARQL_ENDPOINT);\n const doFetch = options.fetchImpl ?? fetch;\n\n const form = new URLSearchParams();\n form.set(\"query\", options.query);\n // The endpoint answers in XML when only the Accept header asks for JSON, so\n // the format is stated in the request itself as well.\n form.set(\"format\", \"application/sparql-results+json\");\n\n // Redirects are handled here rather than by fetch. Following one automatically\n // would take the request to whatever host the answer names, and the guard\n // above only ever sees the address this file wrote: a redirect to Gallica\n // would cross the licence boundary with no code having decided to.\n let target = SPARQL_ENDPOINT;\n for (let hop = 0; hop <= MAX_REDIRECTS; hop += 1) {\n const response = await doFetch(target, {\n method: \"POST\",\n signal,\n redirect: \"manual\",\n headers: {\n \"user-agent\": options.userAgent,\n accept: \"application/sparql-results+json\",\n \"content-type\": \"application/x-www-form-urlencoded\",\n },\n body: form.toString(),\n });\n\n const location = response.headers.get(\"location\");\n if (response.status >= 300 && response.status < 400 && location !== null) {\n const next = new URL(location, target).toString();\n // Checked before the next request rather than after it, so a refused\n // address is one this client never called.\n assertRequestable(next);\n target = next;\n continue;\n }\n\n return {\n status: response.status,\n headers: response.headers,\n body: await response.text(),\n ok: response.ok,\n };\n }\n\n throw networkError(`data.bnf.fr redirected this request more than ${MAX_REDIRECTS} times.`, {\n url: SPARQL_ENDPOINT,\n });\n}\n\n/** What a refusal from the endpoint amounts to, and what it costs the pacing. */\ntype Refusal =\n | { kind: \"refused\"; error: Error; pushBack: boolean }\n | { kind: \"again\"; waitMs: number; pushBack: boolean };\n\n/**\n * Read a status the endpoint answered with, apart from the loop that retries.\n *\n * Anything worth another attempt means the service is struggling, so the gap\n * widens whether or not the status is one that names a delay: a Virtuoso under\n * load answers 500 as readily as 503. A status the service read the query for\n * and would not run is not a network failure, and calling it one invites a\n * retry of something only the query can fix.\n */\nfunction readRefusal(\n response: { status: number; body: string; headers: Headers },\n attempt: number,\n maxRetries: number,\n): Refusal {\n const pushBack = RETRYABLE.has(response.status);\n\n if (PUSH_BACK.has(response.status)) {\n const asked = parseRetryAfter(response.headers.get(\"retry-after\"));\n\n if (asked !== null && asked > LONGEST_WAIT_MS) {\n return {\n kind: \"refused\",\n pushBack,\n error: rateLimited(\n `data.bnf.fr asked this client to wait ${Math.round(asked / 1000)} seconds (HTTP ${response.status}).`,\n { url: SPARQL_ENDPOINT, status: response.status },\n ),\n };\n }\n if (attempt >= maxRetries) {\n return {\n kind: \"refused\",\n pushBack,\n error: rateLimited(\n `data.bnf.fr asked this client to slow down (HTTP ${response.status}).`,\n {\n url: SPARQL_ENDPOINT,\n status: response.status,\n },\n ),\n };\n }\n return { kind: \"again\", pushBack, waitMs: asked ?? backoffMs(attempt) };\n }\n\n if (RETRYABLE.has(response.status) && attempt < maxRetries) {\n return { kind: \"again\", pushBack, waitMs: backoffMs(attempt) };\n }\n\n if (response.status === 400 || response.status === 422) {\n const engine = readEngineError(response.body);\n return {\n kind: \"refused\",\n pushBack,\n error: invalidInput(\n `data.bnf.fr would not run this query${engine?.text ? `: ${engine.text}` : \".\"}`,\n \"Try fewer or plainer words. If this keeps happening with ordinary input, please report it.\",\n ),\n };\n }\n\n return {\n kind: \"refused\",\n pushBack,\n error: networkError(`data.bnf.fr answered HTTP ${response.status}.`, {\n url: SPARQL_ENDPOINT,\n status: response.status,\n }),\n };\n}\n\n/**\n * What a thrown attempt amounts to, or the error it has become.\n *\n * An error this module raised on purpose already says what happened, and is\n * passed straight on. Silence is given fewer attempts than a refusal: a query\n * spanning the whole catalogue can take longer than the service will spend on\n * it, and asking again costs both sides the same wait.\n */\nfunction readFailure(\n error: unknown,\n attempts: { attempt: number; maxRetries: number; timeoutMs: number },\n): Error {\n const { attempt, maxRetries, timeoutMs } = attempts;\n\n if (error instanceof Error && error.name === \"BnfError\") {\n throw error;\n }\n\n if (error instanceof Error && error.name === \"AbortError\") {\n if (attempt >= Math.min(maxRetries, RETRIES_AFTER_SILENCE)) {\n throw timeoutError(\n `No answer from data.bnf.fr within ${timeoutMs}ms. A query spanning the whole catalogue can take longer than the service will spend on it.`,\n { url: SPARQL_ENDPOINT },\n );\n }\n return error;\n }\n\n const failure = error instanceof Error ? error : new Error(String(error));\n if (attempt >= maxRetries) {\n throw networkError(`Could not reach data.bnf.fr: ${failure.message}`, { url: SPARQL_ENDPOINT });\n }\n return failure;\n}\n\n/** Send one query and return the parsed result set. */\nexport async function runQuery(options: QueryOptions): Promise<SparqlResults> {\n const { timeoutMs, maxRetries, limiter, logger } = options;\n\n let lastError: Error | null = null;\n /** Honoured before the next attempt rather than slept after the last one. */\n let askedWaitMs = 0;\n\n for (let attempt = 0; attempt <= maxRetries; attempt += 1) {\n if (askedWaitMs > 0) {\n logger.debug(`waiting ${askedWaitMs}ms, as asked`);\n // Read once into a constant: the timer closes over what this attempt was\n // told to wait, not over whatever a later attempt puts there.\n const asked = askedWaitMs;\n await new Promise((resolve) => setTimeout(resolve, asked));\n askedWaitMs = 0;\n }\n await limiter.beforeRequest();\n\n const controller = new AbortController();\n const deadline = setTimeout(() => controller.abort(), timeoutMs);\n\n try {\n logger.debug(`POST ${SPARQL_ENDPOINT} (${options.query.length} characters)`);\n const response = await postOnce(options, controller.signal);\n\n if (response.ok) {\n // The body decides whether this was an answer. This endpoint says \"I\n // will spend no more on you\" with a status of 200 and either an empty\n // body or a runtime error in plain text, so crediting the status line\n // would narrow the spacing at the one moment it has to widen.\n let parsed: SparqlResults;\n try {\n parsed = parseResults(response.body);\n } catch (error) {\n limiter.pushBack();\n throw error;\n }\n limiter.succeeded();\n return parsed;\n }\n\n const verdict = readRefusal(response, attempt, maxRetries);\n if (verdict.pushBack) {\n limiter.pushBack();\n }\n if (verdict.kind === \"refused\") {\n throw verdict.error;\n }\n askedWaitMs = verdict.waitMs;\n lastError = new Error(`HTTP ${response.status}`);\n } catch (error) {\n clearTimeout(deadline);\n\n lastError = readFailure(error, { attempt, maxRetries, timeoutMs });\n askedWaitMs = backoffMs(attempt);\n } finally {\n clearTimeout(deadline);\n }\n }\n\n throw networkError(\n `Could not reach data.bnf.fr: ${lastError?.message ?? \"no attempt was made\"}`,\n {\n url: SPARQL_ENDPOINT,\n },\n );\n}\n\n/**\n * Read a result set out of a body that arrived with a success status.\n *\n * The endpoint answers 200 with an empty body when it abandons a query part way\n * through, which is the same silence as a result set holding no rows and means\n * the opposite. An empty body is therefore a failure to read rather than an\n * absence, so nothing downstream can report \"the BnF describes none\" on the\n * strength of a query that was never finished.\n */\nexport function parseResults(body: string): SparqlResults {\n if (body.trim() === \"\") {\n throw parseFailure(\n \"data.bnf.fr returned an empty body, which is what it does when it abandons a query part way through.\",\n { url: SPARQL_ENDPOINT },\n );\n }\n\n const engine = readEngineError(body);\n if (engine) {\n if (engine.kind === \"compile\") {\n throw invalidInput(\n `data.bnf.fr could not read this query: ${engine.text}`,\n \"Try fewer or plainer words. If this keeps happening with ordinary input, please report it.\",\n );\n }\n throw timeoutError(`data.bnf.fr started this query and gave it up: ${engine.text}`, {\n url: SPARQL_ENDPOINT,\n });\n }\n\n let payload: unknown;\n try {\n payload = JSON.parse(body);\n } catch (cause) {\n throw parseFailure(\"data.bnf.fr answered with something that is not JSON.\", {\n url: SPARQL_ENDPOINT,\n cause,\n });\n }\n\n if (\n typeof payload !== \"object\" ||\n payload === null ||\n !(\"results\" in payload) ||\n typeof (payload as SparqlResults).results !== \"object\" ||\n !Array.isArray((payload as SparqlResults).results?.bindings)\n ) {\n throw parseFailure(\"data.bnf.fr answered with JSON that is not a SPARQL result set.\", {\n url: SPARQL_ENDPOINT,\n });\n }\n\n return payload as SparqlResults;\n}\n","/**\n * How caller text becomes part of a query, and what it can never become.\n *\n * A query is a program, and every character of it that came from outside is a\n * chance to write a different program. Three separate defences apply here, and\n * each is narrower than the one before:\n *\n * 1. Text that reaches the full-text index is reduced to words. Letters,\n * digits, apostrophes inside a word and hyphens inside a word survive; every\n * quote, brace, backslash, angle bracket and newline is dropped before the\n * query exists. There is no escaping to get right because there is nothing\n * left to escape.\n * 2. An identifier is matched against the two shapes this dataset uses and\n * rebuilt from the match, so the address in the query is one this file\n * wrote rather than one a caller supplied.\n * 3. Numbers reach the query as numbers, after being bounded.\n *\n * `escapeLiteral` exists for the values that are neither: it is the last line\n * rather than the first, and the tests hold it to producing a literal that\n * closes where it opened whatever it is handed.\n */\n\nimport { invalidInput } from \"../errors.js\";\n\n/** What a term can carry: letters, digits, the joiners and the spaces between them. */\nconst KEPT_CHARACTER = /[\\p{L}\\p{N}'\\-\\s]/u;\nconst TYPOGRAPHIC_APOSTROPHE = /[‘’ʼ]/u;\nconst LEADING_JOINERS = /^['-]+/u;\nconst TRAILING_JOINERS = /['-]+$/u;\nconst JOINERS = /['-]+/u;\nconst ARK_IN_INPUT = /(?:^|\\/)ark:\\/12148\\/(c[bc][0-9a-z]+)/i;\nconst LEADING_HASH = /^#/;\nconst FRAGMENT_OR_QUERY = /[#?]/;\nconst TEMP_WORK_IN_INPUT = /temp-work\\/([0-9a-f]{32})/i;\nconst GALLICA_ARK = /ark:\\/12148\\/([0-9a-z]+)/i;\n\n/**\n * Escape a string for a SPARQL short literal in double quotes.\n *\n * Backslash goes first: escaping it after the others would escape the\n * backslashes they just introduced. The characters a SPARQL short literal\n * cannot carry raw are a quote, a backslash, a line feed and a carriage\n * return; the rest are legal and pass through as published, since mangling a\n * title is its own kind of lie.\n */\nexport function escapeLiteral(value: string): string {\n return (\n value\n .replace(/\\\\/g, \"\\\\\\\\\")\n .replace(/\"/g, '\\\\\"')\n .replace(/\\n/g, \"\\\\n\")\n .replace(/\\r/g, \"\\\\r\")\n .replace(/\\t/g, \"\\\\t\")\n // The remaining control characters are legal inside a SPARQL literal and\n // meaningless in a catalogue query. A null byte in particular is where a\n // C string ends, so an intermediary or the engine can truncate the query\n // at one and run a shorter query than the one that was written.\n .replace(/[\\u0000-\\u0008\\u000b\\u000c\\u000e-\\u001f\\u007f]/gu, \"\")\n );\n}\n\n/** A literal, quoted and escaped, ready to be dropped into a query. */\nexport const literal = (value: string): string => `\"${escapeLiteral(value)}\"`;\n\n/**\n * Characters that occupy a position in a string and mark nothing on a screen.\n *\n * The control characters other than the whitespace ones, the zero-width spaces\n * and joiners, the soft hyphen, the bidirectional marks and the byte order\n * mark. A space, a tab and a line break are absent from the list: they are\n * visible as a gap and they do separate two words.\n *\n * They are written as escapes because they would be invisible in this file as\n * well, and a character nobody can see in the source of a guard is a guard\n * nobody can review.\n */\nconst INVISIBLE =\n /[\\u0000-\\u0008\\u000e-\\u001f\\u007f-\\u009f\\u00ad\\u061c\\u180e\\u200b-\\u200f\\u202a-\\u202e\\u2060-\\u2064\\u2066-\\u2069\\ufeff]/gu;\n\n/**\n * The text with the characters that mark nothing removed.\n *\n * A separator is something a person wrote to hold two words apart. One of these\n * holds nothing apart, so treating it as a separator turns one word into two\n * terms the index then requires separately, and a name that matches comes back\n * as a name the catalogue does not hold.\n */\nexport function withoutInvisibles(input: string): string {\n return input.replace(INVISIBLE, \"\");\n}\n\n/** The characters that marked nothing, listed once each, in the order written. */\nexport function invisibleCharacters(input: string): string[] {\n const found: string[] = [];\n for (const character of input.match(INVISIBLE) ?? []) {\n if (!found.includes(character)) {\n found.push(character);\n }\n }\n return found;\n}\n\n/**\n * The visible characters a search dropped, listed once each.\n *\n * Punctuation reaches no index here, so a caller comparing what they wrote\n * against what was searched for needs to see which marks were left out. A\n * space is not one of them: it separates words and is what the words are cut\n * along.\n */\nexport function charactersSetAside(input: string): string[] {\n const found: string[] = [];\n for (const character of withoutInvisibles(input).normalize(\"NFC\")) {\n if (KEPT_CHARACTER.test(character) || TYPOGRAPHIC_APOSTROPHE.test(character)) {\n continue;\n }\n if (!found.includes(character)) {\n found.push(character);\n }\n }\n return found;\n}\n\n/** How a piece of caller text became the terms the index was given. */\nexport interface SearchReading {\n /** The words the text was cut into, each one quoted into the query. */\n words: string[];\n /** The terms the index requires, after it splits inside a word. */\n terms: string[];\n /** The characters that marked nothing and were removed before the cutting. */\n invisible: string[];\n /** The visible characters that reached no term. */\n setAside: string[];\n}\n\n/** Read a piece of caller text the way the query builder and the index will. */\nexport function readSearchText(input: string): SearchReading {\n const words = toSearchWords(input);\n return {\n words,\n terms: toIndexTerms(words),\n invisible: invisibleCharacters(input),\n setAside: charactersSetAside(input),\n };\n}\n\n/**\n * Words a full-text query may carry.\n *\n * The endpoint is a Virtuoso, whose `bif:contains` takes its own miniature\n * language: words in single quotes joined by AND, OR and NOT, with `*` for a\n * prefix. Handing caller text to that language directly would let a caller\n * write the operators, so the text is cut into words instead and this file\n * writes every operator.\n *\n * A word keeps letters and digits in any script, and keeps an apostrophe or a\n * hyphen that sits between two other characters, because \"aujourd'hui\" and\n * \"Charleville-Mézières\" are one word each. A leading or trailing one is\n * dropped, so nothing can close the quoting this file opens.\n *\n * The same rule holds a span of years together: \"1871-1933\" stays one word.\n * Nothing here can tell a range from a compound name, and splitting on the\n * chance that it is a range would break every hyphenated place in France.\n */\nexport function toSearchWords(input: string): string[] {\n const cleaned = withoutInvisibles(input)\n .normalize(\"NFC\")\n // Anything that is not a letter, a digit, an apostrophe or a hyphen becomes\n // a separator. Typographic apostrophes are folded onto the plain one first,\n // since a name is written with either.\n .replace(/[‘’ʼ]/gu, \"'\")\n .replace(/[^\\p{L}\\p{N}'-]+/gu, \" \");\n\n const words: string[] = [];\n for (const raw of cleaned.split(\" \")) {\n // Trim the joiners at the edges: they only mean something between letters.\n const word = raw.replace(LEADING_JOINERS, \"\").replace(TRAILING_JOINERS, \"\");\n if (word === \"\") {\n continue;\n }\n // A word this long is a paste rather than a word, and the index will not\n // match it anyway.\n words.push(word.length > 60 ? word.slice(0, 60) : word);\n if (words.length >= 12) {\n break;\n }\n }\n return words;\n}\n\n/**\n * The terms the index will require, read off the words a caller wrote.\n *\n * The index tokenises inside a quoted term: an apostrophe and a hyphen are\n * separators there, so `O'Brien` reaches it as two terms and a record written\n * `Pat O Brien` carries both and matches. Reporting the word back as one term\n * would leave a caller checking why a row is on the list with no way of getting\n * there from the fields shown.\n *\n * A piece repeated across words is kept once, since requiring a term twice is\n * requiring it.\n */\nexport function toIndexTerms(words: readonly string[]): string[] {\n const terms: string[] = [];\n for (const word of words) {\n for (const piece of word.split(JOINERS)) {\n if (piece !== \"\" && !terms.includes(piece)) {\n terms.push(piece);\n }\n }\n }\n return terms;\n}\n\n/**\n * A `bif:contains` argument requiring every word, built from words this file\n * quoted.\n *\n * The result is a SPARQL literal, so the double quotes belong to SPARQL and the\n * single quotes belong to the index. Two quotings are therefore in play, and\n * each has to be closed by the layer that opened it.\n *\n * The apostrophe kept inside a word is also the index's own quoting character,\n * so `rimbaud'OR'hugo` arrives as one word and emits `'rimbaud'OR'hugo'`, where\n * `OR` has escaped the quotes and become an operator. Doubling it puts it back\n * inside the term, which is how the index writes a literal quote. That is the\n * whole of the exposure: the word cannot reach the SPARQL layer as anything but\n * text, because `escapeLiteral` runs over the finished clause.\n */\nexport function containsAllWords(words: readonly string[]): string {\n if (words.length === 0) {\n throw invalidInput(\n \"No word to search for: the text held no letters or digits.\",\n \"Write the words to look for, such as a surname or a title.\",\n );\n }\n const clause = words.map((word) => `'${word.replace(/'/g, \"''\")}'`).join(\" AND \");\n return literal(clause);\n}\n\n/**\n * The two identifier shapes this dataset uses, and nothing else.\n *\n * An ARK identifier is `cb` followed by digits and one final check character,\n * which the BnF also writes as `cc…` on a few records. A provisional work is\n * named by a thirty-two character hexadecimal digest. Both are matched whole,\n * so an identifier reaches a query only after this file has rebuilt it.\n */\nconst ARK_ID = /^c[bc][0-9a-z]{6,20}$/;\nconst TEMP_WORK_ID = /^[0-9a-f]{32}$/;\n\nexport type EntityKind = \"ark\" | \"temp-work\";\n\nexport interface EntityId {\n kind: EntityKind;\n /** The bare identifier, as a person would quote it. */\n id: string;\n /** The address of the thing itself, which the queries bind to. */\n iri: string;\n /** The page a person can open. */\n pageUrl: string;\n}\n\n/**\n * Read an identifier a caller wrote, in any of the forms they see.\n *\n * Rows carry `http://data.bnf.fr/ark:/12148/cb119219976#about`, prose carries\n * `cb119219976`, and a browser carries `https://data.bnf.fr/ark:/12148/cb119219976`.\n * All three name one record. Anything else is refused rather than guessed at,\n * because an identifier built from a guess sends the next call to a record that\n * does not exist and the answer reads as an absence.\n */\nexport function parseEntityId(input: string): EntityId {\n const trimmed = input.trim();\n if (trimmed === \"\") {\n throw invalidInput(\"An identifier is required.\");\n }\n\n const arkMatch = ARK_IN_INPUT.exec(trimmed);\n const bare = arkMatch\n ? arkMatch[1]\n : trimmed.replace(LEADING_HASH, \"\").split(FRAGMENT_OR_QUERY)[0];\n const candidate = (bare ?? \"\").toLowerCase();\n\n if (ARK_ID.test(candidate)) {\n return {\n kind: \"ark\",\n id: candidate,\n iri: `http://data.bnf.fr/ark:/12148/${candidate}#about`,\n pageUrl: `https://data.bnf.fr/ark:/12148/${candidate}`,\n };\n }\n\n const tempMatch = TEMP_WORK_IN_INPUT.exec(trimmed);\n const digest = (tempMatch?.[1] ?? candidate).toLowerCase();\n if (TEMP_WORK_ID.test(digest)) {\n return {\n kind: \"temp-work\",\n id: `temp-work/${digest}`,\n iri: `http://data.bnf.fr/temp-work/${digest}/#about`,\n pageUrl: `https://data.bnf.fr/temp-work/${digest}/`,\n };\n }\n\n throw invalidInput(`\"${input}\" is not a data.bnf.fr identifier.`, hintFor(trimmed));\n}\n\n/**\n * Two shapes people paste, which are addresses of something else entirely.\n *\n * A Gallica document identifier and a data.bnf.fr page address both look like\n * identifiers to whoever is holding one, and both are refused here. Refusing\n * them with the general advice reads as the server failing to know a record it\n * plainly has, so each is named for what it is and told what to do instead.\n */\nconst GALLICA_DOCUMENT = /^(?:bpt6k|btv1b|bd6t|cb32|btv1b)[0-9a-z]+$/i;\n/** `https://data.bnf.fr/fr/11907966/marguerite_duras/`, which is what a browser shows. */\nconst PAGE_ADDRESS = /data\\.bnf\\.fr\\/(?:[a-z]{2}\\/)?(\\d{6,12})\\/([a-z0-9_]+)/i;\n\nconst GENERAL_HINT =\n \"An identifier looks like 'cb119219976' for an established record, or 'temp-work/22d7f68c1a4bdd081ad7ca791fd3b730' for a provisional one. Take it from a search result rather than building one.\";\n\nfunction hintFor(input: string): string {\n const page = PAGE_ADDRESS.exec(input);\n if (page) {\n const name = (page[2] ?? \"\").replace(/_/g, \" \");\n return (\n `That is the readable address of a data.bnf.fr page, and ${page[1]} in it is the record's FRBNF number rather than the identifier these tools take. ` +\n `Search for \"${name}\" with search_authors or search_works, and use the identifier the row carries.`\n );\n }\n\n const gallicaArk = GALLICA_ARK.exec(input)?.[1] ?? input;\n if (GALLICA_DOCUMENT.test(gallicaArk)) {\n return (\n \"That is a Gallica identifier: it names a digitised document rather than a record in the catalogue, and these tools read the catalogue. \" +\n \"Search for the work with search_works, then list_editions will give you the Gallica link alongside the edition it belongs to.\"\n );\n }\n\n return GENERAL_HINT;\n}\n\n/** An identifier written back as the address of the entity itself. */\nexport const iriOf = (id: EntityId): string => `<${id.iri}>`;\n\n/**\n * A whole number, bounded, written into a query.\n *\n * LIMIT and OFFSET take a bare number rather than a quoted value, so this is\n * the one place where something that is not a literal is assembled. Bounding\n * before writing is what makes that safe, and it also keeps a caller from\n * asking the endpoint for a page a hundred thousand rows deep.\n */\nexport function boundedInteger(value: number, min: number, max: number, name: string): number {\n if (!(Number.isFinite(value) && Number.isInteger(value))) {\n throw invalidInput(`'${name}' has to be a whole number.`);\n }\n if (value < min || value > max) {\n throw invalidInput(`'${name}' has to be between ${min} and ${max}.`);\n }\n return value;\n}\n","/**\n * The queries this server sends, and the reasons they are shaped as they are.\n *\n * Every one of them describes the endpoint as it actually answers: the shapes\n * below are the ones the live service returns, rather than the ones the\n * vocabularies promise.\n *\n * Three facts about this dataset govern most of what follows.\n *\n * **An entity and its record are two addresses.** A person is\n * `ark:/12148/cb119219976#about`, typed `foaf:Person`, and carries the dates,\n * the places and the alignments. The authority record is the same ARK without\n * the fragment, typed `skos:Concept`, and carries the headings, the editorial\n * notes and the date the record was last changed. A query that reads only one\n * of the two misses half the record.\n *\n * **Work, expression and manifestation are three levels.** A work links to\n * expressions, and a manifestation names both the expression it realises and\n * the work it manifests. `workManifested` is what makes the editions of a work\n * reachable in one step rather than two.\n *\n * **The full-text index drives, or the query is abandoned.** Virtuoso needs the\n * triple whose object it will search to appear before `bif:contains` in the same\n * group, and it needs that pattern to be the one that starts the plan. Asking\n * for `?w a rdafrbr:Work` first makes the planner walk every work in the\n * catalogue and the service gives the query up, answering 200 with an empty\n * body. Putting the text search in a subquery and filtering its output is what\n * keeps the index in front.\n *\n * **A paged query is ordered at both levels.** The subquery decides which\n * entities a page holds; the outer query decides the sequence its rows arrive\n * in, and SPARQL gives an unordered outer query no sequence at all. Since a\n * page is read by taking one entity beyond it and dropping the last, an outer\n * form without `ORDER BY` drops an entity chosen by the service: that entity is\n * then missing from the walk, and one of its neighbours is served on two pages.\n * Both levels therefore sort on the same variable, so the sequence a caller\n * reads is the sequence the pages were cut along. The outer sort runs over the\n * rows of one page, so it costs a sort of a few dozen rows rather than of the\n * matching set.\n */\n\nimport { PREFIX_BLOCK } from \"./endpoint.js\";\nimport type { EntityId } from \"./sparql.js\";\nimport { boundedInteger, containsAllWords, iriOf, literal } from \"./sparql.js\";\n\n/**\n * How many rows a text search reads from the index before filtering.\n *\n * `bif:contains` answers in index order, which is not an order of relevance, so\n * a window that is too small leaves out the record a caller meant. A window this\n * size costs the service one pass and gives the filter enough to work with.\n *\n * It is also the end of what a text search can page through. An offset past it\n * lands beyond what was ever read, and the answer would be an empty page that\n * looks exactly like the catalogue holding nothing. The tools refuse such a page\n * rather than buying the query, which is why this is exported.\n */\nexport const TEXT_WINDOW = 400;\n\nconst prefixed = (body: string): string => `${PREFIX_BLOCK}\\n${body.trim()}\\n`;\n\n/**\n * How many rows the window returned, asked for in the request that reads it.\n *\n * The window bounds the answer, and it is read before the type filter, so a\n * page of a few dozen rows can rest on a window that came back full. From the\n * page alone the two readings are identical: an index with nothing more to give\n * and an index holding a great deal more that was never asked. The occupancy is\n * what separates them, and it costs one extra pass of the same index inside a\n * request that was already being sent, rather than a second call.\n *\n * It arrives as a row of its own, binding the count and no entity, because a\n * branch of a UNION answers even when the other branch holds nothing. A page\n * emptied by the filter is the reading that most needs the fact.\n */\nfunction windowOccupancy(predicate: string, words: readonly string[]): string {\n return `\n {\n SELECT (COUNT(*) AS ?windowRows) WHERE {\n SELECT DISTINCT ?windowEntity ?windowText WHERE {\n ?windowEntity ${predicate} ?windowText .\n ?windowText bif:contains ${containsAllWords(words)} .\n }\n LIMIT ${TEXT_WINDOW}\n }\n }`;\n}\n\n/**\n * People whose name carries every word given.\n *\n * The name is searched rather than the authority heading, because a heading\n * carries the dates and a caller writing \"Arthur Rimbaud 1854\" would otherwise\n * match on the year. Both are returned, so an answer can show which of several\n * people bearing one name is which.\n *\n * `limit + 1` rows are asked for. The extra row is never shown; it is how the\n * answer can say that more exist without a second query counting them.\n */\nexport function searchAuthorsQuery(\n words: readonly string[],\n limit: number,\n offset: number,\n): string {\n const take = boundedInteger(limit, 1, 50, \"limit\") + 1;\n const skip = boundedInteger(offset, 0, 5000, \"offset\");\n\n // The page is cut in the middle subquery, over people rather than over rows,\n // and the cutting projection names the person alone. Selecting the name\n // alongside would give a person recorded under two names two of the page's\n // slots; the parser then folds them back into one row, and a page of ten\n // comes back holding eight.\n // The columns of the page are read inside the branch that produces it. Read\n // outside it, they would be joined against an unbound subject on the row the\n // occupancy arrives on, which asks the service for every name in the file.\n return prefixed(`\nSELECT ?person ?name ?label ?birthYear ?deathYear ?role ?windowRows WHERE {\n {\n {\n SELECT DISTINCT ?person WHERE {\n {\n SELECT DISTINCT ?person ?name WHERE {\n ?person foaf:name ?name .\n ?name bif:contains ${containsAllWords(words)} .\n }\n LIMIT ${TEXT_WINDOW}\n }\n ?person a foaf:Person .\n }\n ORDER BY ?person\n LIMIT ${take} OFFSET ${skip}\n }\n OPTIONAL { ?person foaf:name ?name }\n OPTIONAL { ?record foaf:focus ?person ; skos:prefLabel ?label }\n OPTIONAL { ?person bnf:firstYear ?birthYear }\n OPTIONAL { ?person bnf:lastYear ?deathYear }\n OPTIONAL { ?person rdag2:biographicalInformation ?role }\n }\n UNION${windowOccupancy(\"foaf:name\", words)}\n}\nORDER BY ?person`);\n}\n\n/**\n * The most triples one record's detail query will read.\n *\n * A record that hits this ceiling has been cut, and a cut record renders as a\n * record whose missing statements are absences. The parsers compare the row\n * count against it and say so rather than reporting the silence as fact.\n */\nexport const MOST_TRIPLES = 2000;\n\n/**\n * Everything both halves of one person's record carry.\n *\n * The two subjects are read in one query through a UNION rather than in two\n * requests: the spacing between requests is the cost that matters here, and one\n * person is one question.\n */\nexport function authorQuery(id: EntityId): string {\n const person = iriOf(id);\n return prefixed(`\nSELECT ?p ?o ?lang WHERE {\n {\n ${person} ?p ?o .\n BIND(LANG(?o) AS ?lang)\n }\n UNION\n {\n ?record foaf:focus ${person} ; ?p ?o .\n BIND(LANG(?o) AS ?lang)\n }\n}\nLIMIT ${MOST_TRIPLES}`);\n}\n\n/**\n * Works whose title carries every word given.\n *\n * Both established works and provisional ones are returned, and the row says\n * which is which: leaving the provisional ones out would hide the only record\n * the BnF holds for a great many recent books, and mixing them in silently\n * would let a provisional record be quoted as an established one.\n */\nexport function searchWorksQuery(words: readonly string[], limit: number, offset: number): string {\n const take = boundedInteger(limit, 1, 50, \"limit\") + 1;\n const skip = boundedInteger(offset, 0, 5000, \"offset\");\n\n // As with people, the page is cut over works rather than over rows, on the\n // work alone: a work with three authors occupies three rows, and a work\n // recorded under two titles would take two of the page's slots.\n return prefixed(`\nSELECT ?work ?title ?date ?status ?creator ?creatorName ?windowRows WHERE {\n {\n {\n SELECT DISTINCT ?work WHERE {\n {\n SELECT DISTINCT ?work ?title WHERE {\n ?work dcterms:title ?title .\n ?title bif:contains ${containsAllWords(words)} .\n }\n LIMIT ${TEXT_WINDOW}\n }\n ?work a rdafrbr:Work .\n }\n ORDER BY ?work\n LIMIT ${take} OFFSET ${skip}\n }\n OPTIONAL { ?work dcterms:title ?title }\n OPTIONAL { ?work dcterms:date ?date }\n OPTIONAL { ?work rdae:statusOfIdentification ?status }\n OPTIONAL {\n ?work dcterms:creator ?creator .\n OPTIONAL { ?creator foaf:name ?creatorName }\n }\n }\n UNION${windowOccupancy(\"dcterms:title\", words)}\n}\nORDER BY ?work`);\n}\n\n/**\n * The works one person is named the creator of.\n *\n * `dcterms:creator` is the statement that ties a work to the person who made\n * it, and it is the widest one at that level: every work reached through the\n * relator vocabularies is either reached through this one as well, or is a\n * record of a performance rather than of a work. So one statement answers the\n * question, and a second path would add rows of a different kind.\n *\n * The page is cut in a subquery over works alone, ordered by their address. A\n * work carrying two form codes arrives as two rows, and cutting the page over\n * rows would give that work two of the page's slots. The order is arbitrary and\n * it is stable, which is what a caller reading page after page needs.\n */\nexport function worksByCreatorQuery(id: EntityId, limit: number, offset: number): string {\n const person = iriOf(id);\n const take = boundedInteger(limit, 1, 50, \"limit\") + 1;\n const skip = boundedInteger(offset, 0, 5000, \"offset\");\n\n return prefixed(`\nSELECT ?work ?title ?date ?year ?status ?form WHERE {\n {\n SELECT DISTINCT ?work WHERE {\n ?work dcterms:creator ${person} .\n }\n ORDER BY ?work\n LIMIT ${take} OFFSET ${skip}\n }\n OPTIONAL { ?work dcterms:title ?title }\n OPTIONAL { ?work dcterms:date ?date }\n OPTIONAL { ?work bnf:firstYear ?year }\n OPTIONAL { ?work rdae:statusOfIdentification ?status }\n OPTIONAL { ?work rdae:formOfWork ?form }\n}\nORDER BY ?work`);\n}\n\n/** Everything one work's record carries, with the names of its creators. */\nexport function workQuery(id: EntityId): string {\n const work = iriOf(id);\n return prefixed(`\nSELECT ?p ?o ?lang ?name WHERE {\n ${work} ?p ?o .\n BIND(LANG(?o) AS ?lang)\n OPTIONAL { ?o foaf:name ?name }\n}\nLIMIT ${MOST_TRIPLES}`);\n}\n\n/**\n * The published editions of one work.\n *\n * A manifestation naming the same work twice, or carrying two digitised copies,\n * comes back as several rows describing one edition. The parser groups them,\n * which is why the manifestation's own address is selected first.\n */\nexport function editionsQuery(id: EntityId, limit: number, offset: number): string {\n const work = iriOf(id);\n const take = boundedInteger(limit, 1, 50, \"limit\") + 1;\n const skip = boundedInteger(offset, 0, 5000, \"offset\");\n\n return prefixed(`\nSELECT ?edition ?title ?date ?year ?publisher ?place ?editionStatement\n ?extent ?isbn ?note ?catalogue ?reproduction ?ocr WHERE {\n {\n SELECT DISTINCT ?edition WHERE {\n ?edition rdarel:workManifested ${work} .\n }\n ORDER BY ?edition\n LIMIT ${take} OFFSET ${skip}\n }\n OPTIONAL { ?edition dcterms:title ?title }\n OPTIONAL { ?edition dcterms:date ?date }\n OPTIONAL { ?edition bnf:firstYear ?year }\n OPTIONAL { ?edition rdae:publishersName ?publisher }\n OPTIONAL { ?edition rdae:placeOfPublication ?place }\n OPTIONAL { ?edition rdae:editionStatement ?editionStatement }\n OPTIONAL { ?edition dcterms:description ?extent }\n OPTIONAL { ?edition bnf:isbn ?isbn }\n OPTIONAL { ?edition rdae:note ?note }\n OPTIONAL { ?edition rdfs:seeAlso ?catalogue }\n OPTIONAL { ?edition rdarel:electronicReproduction ?reproduction }\n OPTIONAL { ?edition bnf:OCR ?ocr }\n}\nORDER BY ?edition`);\n}\n\n/**\n * Every digitised document one work's editions point at.\n *\n * Held apart from the edition listing because the two answer different\n * questions: one asks what was published, the other asks what can be opened.\n */\nexport function digitisedForWorkQuery(id: EntityId, limit: number): string {\n const work = iriOf(id);\n const take = boundedInteger(limit, 1, 200, \"limit\") + 1;\n\n return prefixed(`\nSELECT DISTINCT ?rank ?edition ?title ?reproduction ?ocr ?depiction WHERE {\n {\n ?edition rdarel:workManifested ${work} .\n OPTIONAL { ?edition dcterms:title ?title }\n { ?edition rdarel:electronicReproduction ?reproduction }\n UNION\n { ?edition bnf:OCR ?ocr }\n BIND(0 AS ?rank)\n }\n UNION\n {\n ${work} foaf:depiction ?depiction .\n BIND(${work} AS ?edition)\n BIND(1 AS ?rank)\n }\n}\nORDER BY ?rank\nLIMIT ${take}`);\n}\n\n/**\n * Every digitised document attached to one person.\n *\n * Two paths lead there and they mean different things. `foaf:depiction` hangs\n * off the person and illustrates them. A reproduction hangs off an edition of a\n * work they are credited with, and is that edition digitised. The parser keeps\n * the two apart, and the answer says which is which.\n *\n * Reproductions are ordered first because a well-known person carries dozens of\n * illustrations, and a page filled with those would answer \"what of this author\n * can be read\" with a list of pictures of him.\n */\nexport function digitisedForPersonQuery(id: EntityId, limit: number): string {\n const person = iriOf(id);\n const take = boundedInteger(limit, 1, 200, \"limit\") + 1;\n\n return prefixed(`\nSELECT DISTINCT ?rank ?edition ?title ?reproduction ?ocr ?depiction WHERE {\n {\n ?work dcterms:creator ${person} .\n ?edition rdarel:workManifested ?work .\n OPTIONAL { ?edition dcterms:title ?title }\n { ?edition rdarel:electronicReproduction ?reproduction }\n UNION\n { ?edition bnf:OCR ?ocr }\n BIND(0 AS ?rank)\n }\n UNION\n {\n ${person} foaf:depiction ?depiction .\n BIND(${person} AS ?edition)\n BIND(1 AS ?rank)\n }\n}\nORDER BY ?rank\nLIMIT ${take}`);\n}\n\n/**\n * Whether one address is described at all, and what it is.\n *\n * Asked when a lookup came back with nothing, so an answer can tell \"the BnF\n * describes no such record\" from \"that record exists and is not a person\".\n */\nexport function kindQuery(id: EntityId): string {\n return prefixed(`\nSELECT DISTINCT ?type WHERE {\n ${iriOf(id)} a ?type .\n}\nLIMIT 20`);\n}\n\n/**\n * A query written for the live suite alone, naming one record by its label.\n *\n * It exists so the canary exercises `literal` against text a person typed,\n * rather than only against words the tokeniser produced.\n */\nexport function labelProbeQuery(label: string): string {\n return prefixed(`\nSELECT ?person WHERE {\n ?person foaf:name ${literal(label)} .\n}\nLIMIT 3`);\n}\n","/**\n * Turning result rows into the shapes the tools render.\n *\n * The rule the whole file follows: a statement the record does not carry comes\n * out as `null` or as an empty list, never as a plausible value. A person with\n * no recorded year of death is alive, or unresearched, or died in a year nobody\n * wrote down, and the three are different claims. Only the record decides.\n */\n\nimport { isGallicaAddress, publicPageFor } from \"./endpoint.js\";\nimport { MOST_TRIPLES, TEXT_WINDOW } from \"./queries.js\";\nimport type { SparqlResults, SparqlRow, SparqlTerm } from \"./http.js\";\nimport type {\n AuthorDetail,\n AuthorSummary,\n AuthoredWork,\n DigitisedLink,\n Edition,\n Page,\n WorkDetail,\n WorkSummary,\n} from \"../types.js\";\n\nconst ARK_IN_IRI = /ark:\\/12148\\/(c[bc][0-9a-z]+)/i;\nconst TEMP_WORK_IN_IRI = /temp-work\\/([0-9a-f]{32})/i;\nconst TRAILING_SEPARATOR = /[/#]$/;\nconst PATH_SEPARATOR = /[/#]/;\nconst ARK_SEGMENT = /ark:\\/12148\\/([^/?#]+)/;\nconst QUERY_START = /[?#]/;\nconst LEADING_DOTS = /^[./]+/;\nconst TRAILING_SLASHES = /\\/+$/;\n\n/** Predicates read off a record. Named once, so a rename is one edit. */\nconst P = {\n type: \"http://www.w3.org/1999/02/22-rdf-syntax-ns#type\",\n label: \"http://www.w3.org/2000/01/rdf-schema#label\",\n seeAlso: \"http://www.w3.org/2000/01/rdf-schema#seeAlso\",\n sameAs: \"http://www.w3.org/2002/07/owl#sameAs\",\n exactMatch: \"http://www.w3.org/2004/02/skos/core#exactMatch\",\n closeMatch: \"http://www.w3.org/2004/02/skos/core#closeMatch\",\n prefLabel: \"http://www.w3.org/2004/02/skos/core#prefLabel\",\n altLabel: \"http://www.w3.org/2004/02/skos/core#altLabel\",\n created: \"http://purl.org/dc/terms/created\",\n modified: \"http://purl.org/dc/terms/modified\",\n title: \"http://purl.org/dc/terms/title\",\n date: \"http://purl.org/dc/terms/date\",\n creator: \"http://purl.org/dc/terms/creator\",\n language: \"http://purl.org/dc/terms/language\",\n subject: \"http://purl.org/dc/terms/subject\",\n name: \"http://xmlns.com/foaf/0.1/name\",\n givenName: \"http://xmlns.com/foaf/0.1/givenName\",\n familyName: \"http://xmlns.com/foaf/0.1/familyName\",\n depiction: \"http://xmlns.com/foaf/0.1/depiction\",\n birth: \"http://vocab.org/bio/0.1/birth\",\n death: \"http://vocab.org/bio/0.1/death\",\n firstYear: \"http://data.bnf.fr/ontology/bnf-onto/firstYear\",\n lastYear: \"http://data.bnf.fr/ontology/bnf-onto/lastYear\",\n subjectLabel: \"http://data.bnf.fr/ontology/bnf-onto/subject\",\n isni: \"http://isni.org/ontology#identifierValid\",\n placeOfBirth: \"http://rdvocab.info/ElementsGr2/placeOfBirth\",\n placeOfDeath: \"http://rdvocab.info/ElementsGr2/placeOfDeath\",\n biographicalInformation: \"http://rdvocab.info/ElementsGr2/biographicalInformation\",\n languageOfThePerson: \"http://rdvocab.info/ElementsGr2/languageOfThePerson\",\n countryAssociated: \"http://rdvocab.info/ElementsGr2/countryAssociatedWithThePerson\",\n fieldOfActivity: \"http://rdvocab.info/ElementsGr2/fieldOfActivityOfThePerson\",\n formOfWork: \"http://rdvocab.info/Elements/formOfWork\",\n statusOfIdentification: \"http://rdvocab.info/Elements/statusOfIdentification\",\n /** The occupation, which RDA numbers rather than names. */\n occupation: \"http://rdaregistry.info/Elements/a/#P50113\",\n /** Expressions of a work, which are not editions. */\n expressionOfWork: \"http://rdaregistry.info/Elements/w/#P10078\",\n} as const;\n\n/** Files whose addresses this dataset aligns records with. */\nconst ALIGNMENT_HOSTS: Array<{ host: string; name: string }> = [\n { host: \"viaf.org\", name: \"viaf\" },\n { host: \"idref.fr\", name: \"idref\" },\n { host: \"dbpedia.org\", name: \"dbpedia\" },\n { host: \"wikidata.org\", name: \"wikidata\" },\n { host: \"isni.org\", name: \"isni\" },\n { host: \"d-nb.info\", name: \"gnd\" },\n { host: \"id.loc.gov\", name: \"loc\" },\n { host: \"datos.bne.es\", name: \"bne\" },\n { host: \"wikipedia.org\", name: \"wikipedia\" },\n { host: \"musicbrainz.org\", name: \"musicbrainz\" },\n { host: \"francearchives.gouv.fr\", name: \"francearchives\" },\n { host: \"imslp.org\", name: \"imslp\" },\n { host: \"catalogue.bnf.fr\", name: \"catalogue\" },\n];\n\nconst text = (term: SparqlTerm | undefined): string | null => {\n if (!term) {\n return null;\n }\n const value = term.value.trim();\n return value === \"\" ? null : value;\n};\n\nconst integer = (term: SparqlTerm | undefined): number | null => {\n const raw = text(term);\n if (raw === null) {\n return null;\n }\n const value = Number(raw);\n return Number.isInteger(value) ? value : null;\n};\n\n/** The identifier a caller quotes, read back off the address of a record. */\nexport function idFromIri(iri: string): string | null {\n const ark = ARK_IN_IRI.exec(iri);\n if (ark?.[1]) {\n return ark[1].toLowerCase();\n }\n const temp = TEMP_WORK_IN_IRI.exec(iri);\n if (temp?.[1]) {\n return `temp-work/${temp[1].toLowerCase()}`;\n }\n return null;\n}\n\n/** The last segment of a vocabulary address, which is the term itself. */\nconst vocabularyTerm = (iri: string): string =>\n decodeURIComponent(iri.replace(TRAILING_SEPARATOR, \"\").split(PATH_SEPARATOR).pop() ?? iri);\n\n/**\n * A Gallica address turned into a link.\n *\n * Returns null for anything that is not a Gallica address, so a record pointing\n * somewhere else does not arrive labelled as a digitised BnF document. The\n * address is carried through as published: it is what a person will open.\n */\nexport function toDigitisedLink(\n address: string | null,\n role: DigitisedLink[\"role\"],\n fromId: string,\n fromTitle: string | null,\n): DigitisedLink | null {\n if (!(address && isGallicaAddress(address))) {\n return null;\n }\n const segment = ARK_SEGMENT.exec(address)?.[1] ?? null;\n if (!segment) {\n return null;\n }\n // An address can carry a rendering after the name: `.thumbnail` asks for a\n // small image of the document, `.item` for one leaf of it. An ARK name holds\n // no full stop, so what precedes the first one is the identifier and the rest\n // describes a view of it. Reporting the view as the identifier would give two\n // views of one document two identifiers.\n const ark = segment.split(\".\")[0] ?? segment;\n if (ark === \"\") {\n return null;\n }\n return { ark, url: address, rendering: renderingOf(address, ark), role, fromId, fromTitle };\n}\n\n/**\n * The view an address asks for, read off what follows the ARK name in it.\n *\n * `bpt6k90000030` names a document; `bpt6k90000030.thumbnail` asks for a small\n * image of it and `btv1b90000002/f3.item.thumbnail` for one leaf of it as an\n * image. Carrying that as a field of its own is what keeps `url` and `ark` from\n * reading as two names for one thing: a caller told only the ARK would quote a\n * document while having been handed a picture of a page of it.\n */\nfunction renderingOf(address: string, ark: string): string | null {\n const path = address.split(QUERY_START)[0] ?? address;\n const after = path.slice(path.indexOf(`ark:/12148/${ark}`) + `ark:/12148/${ark}`.length);\n const rendering = after.replace(LEADING_DOTS, \"\").replace(TRAILING_SLASHES, \"\");\n return rendering === \"\" ? null : rendering;\n}\n\n/**\n * A page, with the rows beyond it read as more existing.\n *\n * `hasMore` is true when the endpoint sent the extra row that was asked for, and\n * also when a row had to be dropped: a page that came back short because this\n * client could not read part of it is not a page with room to spare.\n */\nfunction page<T>(rows: T[], limit: number, skipped: number): Page<T> {\n const more = rows.length > limit;\n return skipped > 0\n ? { rows: rows.slice(0, limit), hasMore: more, skipped }\n : { rows: rows.slice(0, limit), hasMore: more };\n}\n\n/**\n * Whether the index window a text search reads came back full.\n *\n * The endpoint sends the occupancy on a row of its own, binding the count and\n * naming no entity. A count that reaches the size of the window means the\n * reading stopped where the window did rather than where the matches did.\n *\n * Null when no such row arrived, since a window nobody measured supports no\n * statement about what was left out.\n */\nfunction readWindowOccupancy(results: SparqlResults): boolean | null {\n for (const row of results.results.bindings) {\n const raw = text(row.windowRows);\n if (raw === null) {\n continue;\n }\n const count = Number(raw);\n if (Number.isInteger(count)) {\n return count >= TEXT_WINDOW;\n }\n }\n return null;\n}\n\n/**\n * True for the row carrying the occupancy and nothing else.\n *\n * It names no record, which is the shape of a row that had to be dropped. It is\n * neither: counting it as dropped would report the page as short of a record\n * the endpoint never claimed to send.\n */\nconst isOccupancyRow = (row: SparqlRow, entity: string): boolean =>\n row.windowRows !== undefined && row[entity] === undefined;\n\n/** Index a `?p ?o` result set by predicate, keeping every value. */\nexport function byPredicate(results: SparqlResults): Map<string, SparqlRow[]> {\n const index = new Map<string, SparqlRow[]>();\n for (const row of results.results.bindings) {\n const predicate = text(row.p);\n if (predicate === null) {\n continue;\n }\n const bucket = index.get(predicate);\n if (bucket) {\n bucket.push(row);\n } else {\n index.set(predicate, [row]);\n }\n }\n return index;\n}\n\nconst firstValue = (index: Map<string, SparqlRow[]>, predicate: string): string | null =>\n text(index.get(predicate)?.[0]?.o);\n\nconst allValues = (index: Map<string, SparqlRow[]>, predicate: string): string[] => {\n const seen = new Set<string>();\n for (const row of index.get(predicate) ?? []) {\n const value = text(row.o);\n if (value !== null) {\n seen.add(value);\n }\n }\n return [...seen];\n};\n\n/** Values that are addresses of vocabulary terms, reduced to the term. */\nconst allTerms = (index: Map<string, SparqlRow[]>, predicate: string): string[] =>\n allValues(index, predicate)\n .filter((value) => value.startsWith(\"http\"))\n .map(vocabularyTerm);\n\n/**\n * Dewey classes, and nothing else that happens to share the predicate.\n *\n * `dcterms:subject` on a work points at a Dewey class and at subject-heading\n * ARKs alike. Reducing an ARK to its last segment yields the string `about`,\n * which would then be published as a Dewey class and printed beside a real one.\n */\nconst deweyClasses = (index: Map<string, SparqlRow[]>, predicate: string): string[] =>\n allValues(index, predicate)\n .filter((value) => value.includes(\"dewey.info/class/\"))\n .map(vocabularyTerm);\n\n/** Values that are plain words rather than addresses. */\nconst allWords = (index: Map<string, SparqlRow[]>, predicate: string): string[] =>\n allValues(index, predicate).filter((value) => !value.startsWith(\"http\"));\n\n/**\n * Alignments to the same thing in other files, grouped by file.\n *\n * `owl:sameAs`, `skos:exactMatch` and `skos:closeMatch` are gathered together\n * because a caller wanting the VIAF address does not care which of the three\n * carried it, and the dataset uses all three for the same alignments. A\n * `closeMatch` is a weaker claim than the other two, and the record makes that\n * distinction nowhere a caller could act on it, so the grouping does not\n * pretend otherwise: the field is named for the addresses it holds.\n */\nfunction alignments(\n index: Map<string, SparqlRow[]>,\n selfId: string | null,\n): Record<string, string[]> {\n const found: Record<string, Set<string>> = {};\n for (const predicate of [P.sameAs, P.exactMatch, P.closeMatch, P.seeAlso]) {\n for (const address of allValues(index, predicate)) {\n if (!address.startsWith(\"http\")) {\n continue;\n }\n // An address pointing back at this record says nothing about anywhere\n // else, and the dataset carries several of those per record.\n if (selfId && address.includes(selfId)) {\n continue;\n }\n let host: string;\n try {\n host = new URL(address).hostname.toLowerCase();\n } catch {\n continue;\n }\n const known = ALIGNMENT_HOSTS.find(\n (entry) => host === entry.host || host.endsWith(`.${entry.host}`),\n );\n if (!known) {\n continue;\n }\n const addresses = found[known.name] ?? new Set<string>();\n found[known.name] = addresses;\n addresses.add(address);\n }\n }\n return Object.fromEntries(Object.entries(found).map(([name, set]) => [name, [...set]]));\n}\n\n/** ISNI is written as a bare number rather than an address. */\nfunction isniAddresses(index: Map<string, SparqlRow[]>): string[] {\n return allValues(index, P.isni).map(\n (value) => `https://isni.org/isni/${value.replace(/\\s/g, \"\")}`,\n );\n}\n\n/** Rows of an author search, with the extra row read as \"more exist\". */\nexport function toAuthorSummaries(results: SparqlResults, limit: number): Page<AuthorSummary> {\n const rows: AuthorSummary[] = [];\n const seen = new Set<string>();\n let skipped = 0;\n\n for (const row of results.results.bindings) {\n if (isOccupancyRow(row, \"person\")) {\n continue;\n }\n const iri = text(row.person);\n const id = iri === null ? null : idFromIri(iri);\n if (iri === null || id === null) {\n // A row naming no record, or naming one at an address this client cannot\n // read, is a row that was dropped. Counting it is what keeps a short page\n // from reading as a small catalogue.\n skipped += 1;\n continue;\n }\n if (seen.has(id)) {\n continue;\n }\n seen.add(id);\n rows.push({\n id,\n name: text(row.name),\n label: text(row.label),\n birthYear: integer(row.birthYear),\n deathYear: integer(row.deathYear),\n role: text(row.role),\n sourceUrl: publicPageFor(iri),\n });\n }\n\n return { ...page(rows, limit, skipped), indexWindowFull: readWindowOccupancy(results) };\n}\n\n/** One person, assembled from both halves of their record. */\nexport function toAuthorDetail(results: SparqlResults, id: string, pageUrl: string): AuthorDetail {\n const index = byPredicate(results);\n const types = allValues(index, P.type);\n\n const otherNames: Array<{ label: string; language: string | null }> = [];\n const seenLabels = new Set<string>();\n for (const row of index.get(P.altLabel) ?? []) {\n const label = text(row.o);\n if (label === null || seenLabels.has(label)) {\n continue;\n }\n seenLabels.add(label);\n otherNames.push({ label, language: text(row.lang) });\n }\n\n const sameAs = alignments(index, id);\n const isni = isniAddresses(index);\n if (isni.length > 0) {\n sameAs.isni = [...new Set([...(sameAs.isni ?? []), ...isni])];\n }\n\n const catalogueUrl =\n allValues(index, P.seeAlso).find((address) => address.includes(\"catalogue.bnf.fr\")) ?? null;\n\n const depictions: DigitisedLink[] = [];\n for (const address of allValues(index, P.depiction)) {\n const link = toDigitisedLink(address, \"depiction\", id, null);\n if (link) {\n depictions.push(link);\n }\n }\n\n return {\n id,\n name: firstValue(index, P.name),\n label: firstValue(index, P.prefLabel),\n givenName: firstValue(index, P.givenName),\n familyName: firstValue(index, P.familyName),\n otherNames,\n birthDate: firstValue(index, P.birth),\n deathDate: firstValue(index, P.death),\n birthYear: integer(index.get(P.firstYear)?.[0]?.o),\n deathYear: integer(index.get(P.lastYear)?.[0]?.o),\n birthPlace: firstValue(index, P.placeOfBirth),\n deathPlace: firstValue(index, P.placeOfDeath),\n biographicalInformation: firstValue(index, P.biographicalInformation),\n occupation: firstValue(index, P.occupation),\n languages: allTerms(index, P.languageOfThePerson),\n countries: allTerms(index, P.countryAssociated),\n fields: allWords(index, P.fieldOfActivity),\n deweyClasses: deweyClasses(index, P.fieldOfActivity),\n sameAs,\n catalogueUrl,\n recordCreated: firstValue(index, P.created),\n recordModified: firstValue(index, P.modified),\n depictions,\n types,\n truncated: results.results.bindings.length >= MOST_TRIPLES,\n sourceUrl: pageUrl,\n };\n}\n\n/**\n * How settled a record is.\n *\n * The record states it in words, and the address states it in its shape: a\n * provisional work is addressed under `temp-work` rather than by an ARK. The\n * statement governs when there is one, since a cataloguer can settle a record\n * before it is re-addressed.\n */\nexport function readStatus(statement: string | null, iri: string): \"established\" | \"provisional\" {\n if (statement !== null) {\n const said = statement.toLowerCase();\n if (said.includes(\"provisional\")) {\n return \"provisional\";\n }\n if (said.includes(\"established\")) {\n return \"established\";\n }\n }\n return iri.includes(\"/temp-work/\") ? \"provisional\" : \"established\";\n}\n\n/** Rows of a work search, gathering the creators of each work. */\nexport function toWorkSummaries(results: SparqlResults, limit: number): Page<WorkSummary> {\n const order: string[] = [];\n const byId = new Map<string, WorkSummary>();\n\n let skipped = 0;\n for (const row of results.results.bindings) {\n if (isOccupancyRow(row, \"work\")) {\n continue;\n }\n const iri = text(row.work);\n const id = iri === null ? null : idFromIri(iri);\n if (iri === null || id === null) {\n // A row naming no record, or naming one at an address this client cannot\n // read, is a row that was dropped. Counting it is what keeps a short page\n // from reading as a small catalogue.\n skipped += 1;\n continue;\n }\n\n let work = byId.get(id);\n if (!work) {\n work = {\n id,\n title: text(row.title),\n date: text(row.date),\n creators: [],\n status: readStatus(text(row.status), iri),\n sourceUrl: publicPageFor(iri),\n };\n byId.set(id, work);\n order.push(id);\n }\n\n const creatorIri = text(row.creator);\n const creatorId = creatorIri ? idFromIri(creatorIri) : null;\n if (creatorId && !work.creators.some((c) => c.id === creatorId)) {\n work.creators.push({ id: creatorId, name: text(row.creatorName) });\n }\n }\n\n return {\n ...page(\n order.map((id) => byId.get(id)).filter((one) => one !== undefined),\n limit,\n skipped,\n ),\n indexWindowFull: readWindowOccupancy(results),\n };\n}\n\n/**\n * The works one person is credited with, gathered from rows that repeat.\n *\n * A work pointing at two form terms arrives as two rows differing in one\n * column. Counting rows would report two works where the catalogue links one,\n * so the address of the work is what a work is counted by.\n */\nexport function toAuthoredWorks(results: SparqlResults, limit: number): Page<AuthoredWork> {\n const order: string[] = [];\n const byId = new Map<string, AuthoredWork>();\n\n let skipped = 0;\n for (const row of results.results.bindings) {\n const iri = text(row.work);\n const id = iri === null ? null : idFromIri(iri);\n if (iri === null || id === null) {\n // A row naming no record, or naming one at an address this client cannot\n // read, is a row that was dropped. Counting it is what keeps a short page\n // from reading as a small catalogue.\n skipped += 1;\n continue;\n }\n\n let work = byId.get(id);\n if (!work) {\n work = {\n id,\n title: text(row.title),\n date: text(row.date),\n year: integer(row.year),\n forms: [],\n status: readStatus(text(row.status), iri),\n sourceUrl: publicPageFor(iri),\n };\n byId.set(id, work);\n order.push(id);\n }\n\n const form = text(row.form);\n if (form?.startsWith(\"http\")) {\n const term = vocabularyTerm(form);\n if (!work.forms.includes(term)) {\n work.forms.push(term);\n }\n }\n }\n\n return page(\n order.map((id) => byId.get(id)).filter((one) => one !== undefined),\n limit,\n skipped,\n );\n}\n\n/** One work, assembled from its record. */\nexport function toWorkDetail(\n results: SparqlResults,\n id: string,\n iri: string,\n pageUrl: string,\n): WorkDetail {\n const index = byPredicate(results);\n const types = allValues(index, P.type);\n const truncated = results.results.bindings.length >= MOST_TRIPLES;\n\n const creators: Array<{ id: string; name: string | null }> = [];\n for (const row of index.get(P.creator) ?? []) {\n const creatorIri = text(row.o);\n const creatorId = creatorIri ? idFromIri(creatorIri) : null;\n if (!creatorId || creators.some((c) => c.id === creatorId)) {\n continue;\n }\n creators.push({ id: creatorId, name: text(row.name) });\n }\n\n const depictions: DigitisedLink[] = [];\n for (const address of allValues(index, P.depiction)) {\n const link = toDigitisedLink(address, \"depiction\", id, null);\n if (link) {\n depictions.push(link);\n }\n }\n\n const statusStatement = firstValue(index, P.statusOfIdentification);\n const catalogueUrl =\n allValues(index, P.seeAlso).find((address) => address.includes(\"catalogue.bnf.fr\")) ?? null;\n\n return {\n id,\n title: firstValue(index, P.title),\n label: firstValue(index, P.label),\n date: firstValue(index, P.date),\n firstYear: integer(index.get(P.firstYear)?.[0]?.o),\n creators,\n languages: allTerms(index, P.language),\n forms: allTerms(index, P.formOfWork),\n subjects: allWords(index, P.subjectLabel),\n deweyClasses: deweyClasses(index, P.subject),\n status: readStatus(statusStatement, iri),\n statusStatement,\n // A cut record makes this the ceiling rather than the count, so it is\n // withheld rather than published as a number the record does not support.\n expressionCount: truncated ? null : allValues(index, P.expressionOfWork).length,\n sameAs: alignments(index, id),\n catalogueUrl,\n depictions,\n types,\n truncated,\n sourceUrl: pageUrl,\n };\n}\n\n/**\n * Editions, gathered from rows that repeat.\n *\n * One manifestation carrying two digitised copies arrives as two rows differing\n * in one column. Counting rows would report two editions where the record\n * describes one, so the address of the manifestation is what an edition is\n * counted by.\n */\nexport function toEditions(results: SparqlResults, limit: number): Page<Edition> {\n const order: string[] = [];\n const byId = new Map<string, Edition>();\n\n let skipped = 0;\n for (const row of results.results.bindings) {\n const iri = text(row.edition);\n const id = iri === null ? null : idFromIri(iri);\n if (iri === null || id === null) {\n // A row naming no record, or naming one at an address this client cannot\n // read, is a row that was dropped. Counting it is what keeps a short page\n // from reading as a small catalogue.\n skipped += 1;\n continue;\n }\n\n let edition = byId.get(id);\n if (!edition) {\n edition = {\n id,\n title: text(row.title),\n date: text(row.date),\n year: integer(row.year),\n publisher: text(row.publisher),\n place: text(row.place),\n editionStatement: text(row.editionStatement),\n extent: text(row.extent),\n isbn: text(row.isbn),\n note: text(row.note),\n catalogueUrl: text(row.catalogue),\n digitised: [],\n sourceUrl: publicPageFor(iri),\n };\n byId.set(id, edition);\n order.push(id);\n }\n\n for (const [column, role] of [\n [\"reproduction\", \"reproduction\"],\n [\"ocr\", \"ocr\"],\n ] as const) {\n const link = toDigitisedLink(text(row[column]), role, id, edition.title);\n if (link && !edition.digitised.some((existing) => existing.url === link.url)) {\n edition.digitised.push(link);\n }\n }\n }\n\n return page(\n order.map((id) => byId.get(id)).filter((one) => one !== undefined),\n limit,\n skipped,\n );\n}\n\n/**\n * Every digitised document a set of rows points at, kept in one list.\n *\n * One document can be reached by several paths, so the same address arrives\n * more than once and is kept once. That makes the length of the list a poor\n * signal of whether more exist: twenty-one rows holding seventeen addresses\n * would otherwise be reported as a page with room to spare. The endpoint was\n * asked for one row beyond the page, so the count of rows is what answers that.\n */\nexport function toDigitisedLinks(results: SparqlResults, limit: number): Page<DigitisedLink> {\n const links: DigitisedLink[] = [];\n const seen = new Set<string>();\n const rowCount = results.results.bindings.length;\n\n for (const row of results.results.bindings) {\n const fromIri = text(row.edition);\n const fromId = fromIri ? (idFromIri(fromIri) ?? fromIri) : \"\";\n const title = text(row.title);\n\n for (const [column, role] of [\n [\"reproduction\", \"reproduction\"],\n [\"ocr\", \"ocr\"],\n [\"depiction\", \"depiction\"],\n ] as const) {\n const link = toDigitisedLink(text(row[column]), role, fromId, title);\n if (!link || seen.has(link.url)) {\n continue;\n }\n seen.add(link.url);\n links.push(link);\n }\n }\n\n // `rowCount` counts what the endpoint sent, including rows whose address was\n // not a Gallica one and rows naming a document already listed. Raising the\n // limit after such a page returns the same list, so the claim is confined to\n // the case where the links themselves fill the page.\n return { rows: links.slice(0, limit), hasMore: links.length > limit || rowCount > limit };\n}\n\n/** The classes an address is typed with, reduced to their vocabulary terms. */\nexport function toTypes(results: SparqlResults): string[] {\n const types = new Set<string>();\n for (const row of results.results.bindings) {\n const iri = text(row.type);\n if (iri !== null) {\n types.add(iri);\n }\n }\n return [...types];\n}\n","/**\n * Keeps this client to one request at a time, spaced out.\n *\n * data.bnf.fr is a query service a public institution runs at its own cost, and\n * a SPARQL endpoint answers an expensive question per request rather than\n * serving a file. Serialising also means a burst of tool calls cannot turn into\n * a burst of concurrent queries on one planner.\n *\n * The spacing widens when the service pushes back and narrows again on a run of\n * quiet successes, so a slow patch does not become the permanent speed.\n */\n\nexport interface RateLimiterOptions {\n /** Spacing between requests when nothing has gone wrong. */\n intervalMs: number;\n /** The widest the spacing may become under push-back. */\n maxIntervalMs?: number;\n}\n\nexport class RateLimiter {\n private readonly baseIntervalMs: number;\n private readonly maxIntervalMs: number;\n private intervalMs: number;\n private lastStartedAt = 0;\n private queue: Promise<void> = Promise.resolve();\n private calmStreak = 0;\n\n constructor(options: RateLimiterOptions) {\n this.baseIntervalMs = options.intervalMs;\n this.maxIntervalMs = options.maxIntervalMs ?? options.intervalMs * 8;\n this.intervalMs = options.intervalMs;\n }\n\n /** The spacing in force, which callers report rather than guess. */\n get currentIntervalMs(): number {\n return this.intervalMs;\n }\n\n /**\n * Run `task` with nothing else in flight.\n *\n * This only serialises. Spacing is claimed per attempt through\n * `beforeRequest`, because a task that retries makes several requests and\n * each of them owes the service the same gap.\n */\n schedule<T>(task: () => Promise<T>): Promise<T> {\n const result = this.queue.then(task, task);\n this.queue = result.then(\n () => undefined,\n () => undefined,\n );\n return result;\n }\n\n /** Wait until enough time has passed since the previous request began. */\n async beforeRequest(): Promise<void> {\n const elapsed = Date.now() - this.lastStartedAt;\n // Capped at one whole interval, so a clock that moved backwards cannot\n // produce a wait longer than the spacing itself.\n const wait = Math.min(this.intervalMs, this.intervalMs - elapsed);\n if (wait > 0) {\n await sleep(wait);\n }\n this.lastStartedAt = Date.now();\n }\n\n /** Called when the service asks for room: double the gap, up to the ceiling. */\n pushBack(): void {\n this.calmStreak = 0;\n this.intervalMs = Math.min(this.maxIntervalMs, this.intervalMs * 2);\n }\n\n /**\n * Called on a clean answer. Recovery takes several in a row, so one lucky\n * response after a rough patch does not undo the caution that earned it.\n */\n succeeded(): void {\n if (this.intervalMs === this.baseIntervalMs) {\n return;\n }\n this.calmStreak += 1;\n if (this.calmStreak < 3) {\n return;\n }\n this.calmStreak = 0;\n this.intervalMs = Math.max(this.baseIntervalMs, Math.round(this.intervalMs / 2));\n }\n}\n\nexport function sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n","/**\n * The one place that talks to data.bnf.fr.\n *\n * It holds a single rate limiter and a single cache, so pacing applies to the\n * server as a whole rather than to whichever tool happens to be running. It\n * imports nothing from the MCP layer and is published on its own, so the same\n * code serves a plain script.\n *\n * Every read runs a query, parses it and only then stores: an answer nobody\n * could parse must not be served back for the rest of the cache's lifetime.\n *\n * Every read also carries the moment the metadata came off the endpoint. The\n * licence asks for the date of retrieval alongside the source, so that date is\n * a value the layer produces rather than something a caller is trusted to add.\n */\n\nimport type { Config, Logger } from \"../config.js\";\nimport {\n MAX_ALLOWED_INTERVAL_MS,\n MIN_ALLOWED_INTERVAL_MS,\n createLogger,\n loadConfig,\n} from \"../config.js\";\nimport { notFound } from \"../errors.js\";\nimport type {\n AuthorDetail,\n AuthorSummary,\n AuthoredWork,\n DigitisedLink,\n Edition,\n Page,\n WorkDetail,\n WorkSummary,\n} from \"../types.js\";\nimport { REPO_URL } from \"../version.js\";\nimport { Cache } from \"./cache.js\";\nimport type { SparqlResults } from \"./http.js\";\nimport { runQuery } from \"./http.js\";\nimport {\n authorQuery,\n digitisedForPersonQuery,\n digitisedForWorkQuery,\n editionsQuery,\n kindQuery,\n searchAuthorsQuery,\n searchWorksQuery,\n workQuery,\n worksByCreatorQuery,\n} from \"./queries.js\";\nimport {\n toAuthorDetail,\n toAuthorSummaries,\n toAuthoredWorks,\n toDigitisedLinks,\n toEditions,\n toTypes,\n toWorkDetail,\n toWorkSummaries,\n} from \"./parse.js\";\nimport { RateLimiter } from \"./rateLimiter.js\";\nimport type { EntityId } from \"./sparql.js\";\nimport { toSearchWords } from \"./sparql.js\";\n\n/** The class data.bnf.fr types a person with. */\nconst PERSON_CLASS = \"http://xmlns.com/foaf/0.1/Person\";\n\n/**\n * The classes data.bnf.fr types a work with.\n *\n * Two vocabularies say the same thing here, and a record carries both. Either\n * one is enough, because a record carrying one has always carried the other.\n */\nconst WORK_CLASSES = new Set([\n \"http://rdvocab.info/uri/schema/FRBRentitiesRDA/Work\",\n \"http://rdaregistry.info/Elements/c/#C10001\",\n]);\n\nexport interface ClientOptions {\n config?: Partial<Config>;\n logger?: Logger;\n fetchImpl?: typeof fetch;\n}\n\n/** Every read reports where the value came from and when it was retrieved. */\nexport interface Read<T> {\n data: T;\n cached: boolean;\n /**\n * When the metadata came off data.bnf.fr, as an ISO 8601 instant.\n *\n * A cached answer keeps the moment it was first read. Reporting the moment it\n * was served would state a date of retrieval that never happened, which is\n * exactly the claim the licence asks to be accurate.\n */\n retrievedAt: string;\n /** Rows the endpoint sent that could not be read, which paging still counts. */\n skipped?: number;\n}\n\n/**\n * What this server owes the BnF, applied to whatever it is handed.\n *\n * A configuration object assembled by a caller has not been through\n * `loadConfig`, so it can carry a missing value, a value of the wrong shape, or\n * a User-Agent that names somebody else. Every bound the environment path\n * enforces is enforced here too: the published `./client` entry point is the\n * one a program uses, and it would be a strange guarantee that held only for\n * the executable.\n *\n * Three of these bounds are about what one call can cost the service. A retry\n * budget of two hundred turns a single lookup into two hundred requests against\n * an endpoint that is already failing. A deadline of an hour holds the one\n * request slot for an hour. A cache of zero entries evicts each value as it is\n * written, which switches off the caching that the three-second floor is set\n * against.\n */\nfunction withGuarantees(config: Config): Config {\n const defaults = loadConfig({});\n\n /**\n * A setting that is absent or unreadable falls back, and one outside its\n * range is brought inside it. Refusing outright is what the environment path\n * does, because a person reads stderr; a program handed an object does not.\n */\n const bounded = (value: unknown, fallback: number, min: number, max: number): number => {\n const parsed = Number(value);\n if (!Number.isFinite(parsed)) {\n return fallback;\n }\n return Math.min(max, Math.max(min, parsed));\n };\n\n const claimed = typeof config.userAgent === \"string\" ? config.userAgent.trim() : \"\";\n const identifier = defaults.userAgent;\n\n return {\n ...config,\n // A caller may say who they are. Appending rather than replacing means the\n // BnF can always tell which software is calling, and reach someone.\n userAgent:\n claimed === \"\" || claimed.includes(REPO_URL) ? identifier : `${claimed} ${identifier}`,\n minIntervalMs: bounded(\n config.minIntervalMs,\n defaults.minIntervalMs,\n MIN_ALLOWED_INTERVAL_MS,\n MAX_ALLOWED_INTERVAL_MS,\n ),\n timeoutMs: bounded(config.timeoutMs, defaults.timeoutMs, 1000, 300_000),\n maxRetries: bounded(config.maxRetries, defaults.maxRetries, 0, 8),\n cacheTtlMs: bounded(config.cacheTtlMs, defaults.cacheTtlMs, 0, 86_400_000),\n cacheMaxEntries: bounded(config.cacheMaxEntries, defaults.cacheMaxEntries, 1, 5000),\n };\n}\n\nexport class BnfClient {\n private readonly config: Config;\n private readonly logger: Logger;\n private readonly limiter: RateLimiter;\n private readonly cache: Cache<unknown>;\n /** Questions already on the wire, so an identical one waits instead of asking. */\n private readonly inFlight = new Map<string, Promise<Read<unknown>>>();\n private readonly fetchImpl: typeof fetch | undefined;\n\n constructor(options: ClientOptions = {}) {\n const base = { ...loadConfig(), ...options.config };\n this.config = withGuarantees(base);\n this.logger = options.logger ?? createLogger(this.config.logLevel);\n this.limiter = new RateLimiter({ intervalMs: this.config.minIntervalMs });\n this.cache = new Cache(this.config.cacheTtlMs, this.config.cacheMaxEntries);\n this.fetchImpl = options.fetchImpl;\n }\n\n /** The pacing in force, which widens when the endpoint pushes back. */\n get intervalMs(): number {\n return this.limiter.currentIntervalMs;\n }\n\n /** What the BnF sees this client call itself. */\n get userAgent(): string {\n return this.config.userAgent;\n }\n\n /** The deadline one query is given, which callers report rather than guess. */\n get timeoutMs(): number {\n return this.config.timeoutMs;\n }\n\n /** The one address this client sends anything to. */\n /**\n * `cacheKey` is stated rather than taken from the query, because two reads of\n * one query can produce different values: a list cut to five rows is not the\n * same value as the same list cut to twenty, and serving one for the other\n * would answer a size the caller never asked for.\n */\n private read<T>(\n cacheKey: string,\n query: string,\n parse: (results: SparqlResults) => T,\n ): Promise<Read<T>> {\n const hit = this.cache.get(cacheKey) as { value: T; retrievedAt: number } | undefined;\n if (hit !== undefined) {\n this.logger.debug(`cache hit ${cacheKey}`);\n return Promise.resolve({\n data: hit.value,\n cached: true,\n retrievedAt: new Date(hit.retrievedAt).toISOString(),\n });\n }\n\n // Two callers asking one question at the same moment both miss the cache,\n // because the first has not answered yet. Sharing the flight means the BnF\n // is asked once, which is the point of the cache in the first place.\n const flying = this.inFlight.get(cacheKey);\n if (flying !== undefined) {\n this.logger.debug(`joining a request already in flight for ${cacheKey}`);\n return flying as Promise<Read<T>>;\n }\n\n const flight = this.fetchAndParse(cacheKey, query, parse);\n this.inFlight.set(cacheKey, flight as Promise<Read<unknown>>);\n // A failure is never remembered, so the next caller asks again rather than\n // being handed the failure of somebody else's request.\n return flight.finally(() => this.inFlight.delete(cacheKey));\n }\n\n private async fetchAndParse<T>(\n cacheKey: string,\n query: string,\n parse: (results: SparqlResults) => T,\n ): Promise<Read<T>> {\n const results = await this.limiter.schedule(() =>\n runQuery({\n query,\n userAgent: this.config.userAgent,\n timeoutMs: this.config.timeoutMs,\n maxRetries: this.config.maxRetries,\n limiter: this.limiter,\n logger: this.logger,\n ...(this.fetchImpl ? { fetchImpl: this.fetchImpl } : {}),\n }),\n );\n\n // The moment is taken once the answer is in hand, so it names when the\n // metadata was read rather than when the request left.\n const retrievedAt = Date.now();\n const data = parse(results);\n this.cache.set(cacheKey, data, retrievedAt);\n return { data, cached: false, retrievedAt: new Date(retrievedAt).toISOString() };\n }\n\n /** Read an identifier a caller wrote, refusing anything this dataset cannot address. */\n /** The words a search will actually look for, which a tool reports back. */\n searchAuthors(name: string, limit: number, offset: number): Promise<Read<Page<AuthorSummary>>> {\n const words = toSearchWords(name);\n const query = searchAuthorsQuery(words, limit, offset);\n return this.read(`authors:${words.join(\"+\")}:${limit}:${offset}`, query, (results) =>\n toAuthorSummaries(results, limit),\n );\n }\n\n async getAuthor(id: EntityId): Promise<Read<AuthorDetail>> {\n const result = await this.read(`author:${id.id}`, authorQuery(id), (results) =>\n toAuthorDetail(results, id.id, id.pageUrl),\n );\n\n // The record decides what it is, and a name is not the test: a subject\n // heading and a corporate body both carry one. Answering with a person\n // record for either would put a date of birth beside something that was\n // never born, and report its absence as a fact about a life.\n if (!result.data.types.includes(PERSON_CLASS)) {\n await this.refuse(id, \"a person\", \"get_work reads a work.\");\n }\n return result;\n }\n\n /**\n * Say what an address is, when it is not the kind of thing that was asked for.\n *\n * A record the catalogue does not describe at all and a record describing\n * something else are two different answers, and only one of them means the\n * caller should go looking elsewhere.\n */\n private async refuse(id: EntityId, wanted: string, hint: string): Promise<never> {\n // A detail query gathers the classes of everything it walks through, so the\n // list it leaves behind is longer on one tool than on another and one\n // record would be described differently depending on which refused it. What\n // the address itself answers is one list, and it is the one every refusal\n // states, whatever the caller was reading at the time.\n const known = (await this.read(`types:${id.id}`, kindQuery(id), toTypes)).data;\n\n if (known.length === 0) {\n throw notFound(`data.bnf.fr describes nothing at \"${id.id}\".`, {\n hint: \"Find the identifier with search_authors or search_works rather than writing one.\",\n url: id.pageUrl,\n });\n }\n throw notFound(\n `\"${id.id}\" is described by data.bnf.fr, and it is not ${wanted}: it is typed ${known.join(\", \")}.`,\n { hint, url: id.pageUrl },\n );\n }\n\n searchWorks(title: string, limit: number, offset: number): Promise<Read<Page<WorkSummary>>> {\n const words = toSearchWords(title);\n const query = searchWorksQuery(words, limit, offset);\n return this.read(`works:${words.join(\"+\")}:${limit}:${offset}`, query, (results) =>\n toWorkSummaries(results, limit),\n );\n }\n\n async getWork(id: EntityId): Promise<Read<WorkDetail>> {\n const result = await this.read(`work:${id.id}`, workQuery(id), (results) =>\n toWorkDetail(results, id.id, id.iri, id.pageUrl),\n );\n\n // An edition carries a title too, and its ARK names a manifestation rather\n // than a work. Accepting one here would hand back an identifier described\n // as the work's, which is the identifier a citation would then carry.\n if (!result.data.types.some((type) => WORK_CLASSES.has(type))) {\n await this.refuse(\n id,\n \"a work\",\n \"get_author reads a person, and list_editions reads the editions of a work.\",\n );\n }\n return result;\n }\n\n /**\n * The works one person is named the creator of.\n *\n * The record is not checked here. A person the catalogue credits with nothing\n * and an address that names no person both answer with no rows, and telling\n * them apart costs a second query that only an empty answer needs.\n */\n worksByAuthor(id: EntityId, limit: number, offset: number): Promise<Read<Page<AuthoredWork>>> {\n return this.read(\n `author-works:${id.id}:${limit}:${offset}`,\n worksByCreatorQuery(id, limit, offset),\n (results) => toAuthoredWorks(results, limit),\n );\n }\n\n listEditions(id: EntityId, limit: number, offset: number): Promise<Read<Page<Edition>>> {\n const query = editionsQuery(id, limit, offset);\n return this.read(`editions:${id.id}:${limit}:${offset}`, query, (results) =>\n toEditions(results, limit),\n );\n }\n\n /** Digitised documents attached to a work and to its editions. */\n digitisedForWork(id: EntityId, limit: number): Promise<Read<Page<DigitisedLink>>> {\n return this.read(\n `digitised-work:${id.id}:${limit}`,\n digitisedForWorkQuery(id, limit),\n (results) => toDigitisedLinks(results, limit),\n );\n }\n\n /** Digitised documents attached to a person and to the editions of their works. */\n digitisedForPerson(id: EntityId, limit: number): Promise<Read<Page<DigitisedLink>>> {\n return this.read(\n `digitised-person:${id.id}:${limit}`,\n digitisedForPersonQuery(id, limit),\n (results) => toDigitisedLinks(results, limit),\n );\n }\n\n /** What an address is typed as, which tells a person from a work. */\n types(id: EntityId): Promise<Read<string[]>> {\n return this.read(`types:${id.id}`, kindQuery(id), toTypes);\n }\n}\n\n/**\n * What a caller needs beside the client itself.\n *\n * Every route that reads one record takes an `EntityId` rather than a string,\n * because an identifier built from a guess sends the call to a record that does\n * not exist and the answer reads as an absence. A program importing this entry\n * point therefore has to be able to build one, so the reading of an address is\n * published alongside the client that consumes it.\n */\nexport { parseEntityId } from \"./sparql.js\";\nexport type { EntityId, EntityKind } from \"./sparql.js\";\n","/**\n * find_digitised: every digitised document the catalogue attaches to one\n * person or one work, gathered as links.\n *\n * The links point at gallica.bnf.fr, and this server never requests that host.\n * The BnF places its metadata and its digitised contents under two different\n * regimes: the catalogue described here is free to reuse with its source and\n * its date of retrieval named, while Gallica's terms make use of its contents\n * inside an artificial-intelligence project subject to a paid licence outside\n * academic research, and its server refuses automated agents outright.\n *\n * So the answer is a list of addresses and what the catalogue says about each,\n * and nothing at all about what is at the other end. An OCR link in particular\n * names a machine-read text that exists; the text stays where it is.\n */\n\nimport { z } from \"zod\";\nimport type { BnfClient } from \"../bnf/client.js\";\nimport { invalidInput, notFound } from \"../errors.js\";\nimport type { DigitisedLink } from \"../types.js\";\nimport { parseEntityId } from \"../bnf/sparql.js\";\nimport { strictInput } from \"./arguments.js\";\nimport {\n GALLICA_CAVEAT,\n digitisedLinkSchema,\n ok,\n recordKindOf,\n retrievedAtSchema,\n toToolError,\n} from \"./shared.js\";\nimport type { ToolResult } from \"./shared.js\";\nimport type { EntityId } from \"../bnf/sparql.js\";\n\n/**\n * Whether the catalogue describes a person or a work at this address.\n *\n * The path a person is followed by and the path a work is followed by reach\n * different links, so the wrong one answers with part of the record or with\n * none of it. The name is only worth reading if the catalogue is what settled\n * it, so the type is read here whatever the caller wrote.\n */\nasync function whichKindTheCatalogueDescribes(\n client: BnfClient,\n id: EntityId,\n): Promise<{ kind: \"person\" | \"work\" } | { error: ToolResult }> {\n // Only a work is addressed under temp-work, so the address settles it.\n if (id.kind === \"temp-work\") {\n return { kind: \"work\" };\n }\n\n const types = await client.types(id);\n if (types.data.length === 0) {\n return {\n error: toToolError(\n notFound(`data.bnf.fr describes nothing at \"${id.id}\".`, {\n hint: \"Find the identifier with search_authors or search_works rather than writing one.\",\n url: id.pageUrl,\n }),\n ),\n };\n }\n\n const found = recordKindOf(types.data);\n if (found === null) {\n return {\n error: toToolError(\n notFound(\n `\"${id.id}\" is described by data.bnf.fr, and it is neither a person nor a work: it is typed ${types.data.join(\", \")}.`,\n {\n hint: \"This tool follows a person or a work. For one edition, list_editions carries the digitised copies alongside the imprint.\",\n url: id.pageUrl,\n },\n ),\n ),\n };\n }\n\n return { kind: found };\n}\n\nexport const findDigitisedDescription = [\n \"Gather the digitised documents the Bibliothèque nationale de France catalogue attaches to one person or one work, and return them as links.\",\n \"Give the identifier of either; the tool asks the catalogue what kind of record it is and follows the right path. For a work it walks the editions; for a person it takes the images on the record and the digitised editions of the works they are credited with. The 'kind' returned is what the catalogue types the record as.\",\n \"Every result is a link for someone to open. This server reads the BnF catalogue and never requests gallica.bnf.fr, so it reports nothing about what is at the other end: not whether the document opens, not what it contains, not on what terms it may be reused.\",\n \"It returns links and nothing else: no publisher, no date, no ISBN. Use list_editions when which edition a copy belongs to matters.\",\n \"A 'depiction' illustrates a record and can be a page that merely mentions the subject. A 'reproduction' is an edition digitised. An 'ocr' link names a machine-read text of a document, which this server does not fetch.\",\n].join(\" \");\n\nexport const findDigitisedInput = strictInput({\n id: z\n .string()\n .min(1)\n .max(200)\n .describe(\n \"A person or work identifier, such as 'cb119219976' or 'cb11970626n'. The argument is named 'id' rather than 'work_id' or 'author_id' because it takes either.\",\n ),\n kind: z\n .enum([\"auto\", \"person\", \"work\"])\n .default(\"auto\")\n .describe(\n \"What you expect the identifier to name. The catalogue is asked either way, and a kind it contradicts is refused rather than followed. 'auto' states no expectation.\",\n ),\n limit: z.number().int().min(1).max(200).default(40),\n});\n\nexport const findDigitisedOutput = z.object({\n id: z.string(),\n kind: z.enum([\"person\", \"work\"]).describe(\"What the catalogue types this record as.\"),\n links: z.array(digitisedLinkSchema),\n links_returned_by_role: z\n .object({\n reproduction: z.number().int(),\n ocr: z.number().int(),\n depiction: z.number().int(),\n })\n .describe(\n \"How many of the links in this answer carry each role. These count the links returned here, which is neither a count of documents nor a count of what the catalogue attaches: several links can name one document, and 'has_more' says when links were left out.\",\n ),\n has_more: z.boolean(),\n retrieved_at: retrievedAtSchema,\n notes: z.array(z.string()),\n});\n\nexport type FindDigitisedArgs = z.infer<typeof findDigitisedInput>;\n\nexport async function runFindDigitised(\n client: BnfClient,\n args: FindDigitisedArgs,\n): Promise<ToolResult> {\n try {\n const id = parseEntityId(args.id);\n\n const notes: string[] = [];\n\n const settled = await whichKindTheCatalogueDescribes(client, id);\n if (\"error\" in settled) {\n return settled.error;\n }\n const kind = settled.kind;\n\n if (args.kind !== \"auto\" && args.kind !== kind) {\n return toToolError(\n invalidInput(\n `The call states that \"${id.id}\" is a ${args.kind}, and data.bnf.fr describes it as a ${kind}.`,\n `Leave 'kind' out, or pass '${kind}'. Following the ${args.kind} path over a ${kind} record answers with part of the record or with none of it, and nothing in that answer would say so.`,\n ),\n );\n }\n\n const { data, cached, retrievedAt } =\n kind === \"person\"\n ? await client.digitisedForPerson(id, args.limit)\n : await client.digitisedForWork(id, args.limit);\n\n if (cached) {\n notes.push(\"Served from this server's short-lived in-memory cache.\");\n }\n\n const links = data.rows.map((link) => ({\n ark: link.ark,\n url: link.url,\n rendering: link.rendering,\n role: link.role,\n from_id: link.fromId,\n from_title: link.fromTitle,\n }));\n\n const counts = {\n reproduction: countRole(data.rows, \"reproduction\"),\n ocr: countRole(data.rows, \"ocr\"),\n depiction: countRole(data.rows, \"depiction\"),\n };\n\n const rendered = links.filter((link) => link.rendering !== null);\n\n if (links.length > 0) {\n notes.push(GALLICA_CAVEAT);\n }\n if (counts.depiction > 0) {\n notes.push(\n `${counts.depiction === 1 ? \"One of these is an image\" : `${counts.depiction} of these are images`} the catalogue uses to illustrate the record. Such an image can be a portrait, a title page, or a newspaper page that mentions the subject in passing, and the catalogue does not say which.`,\n );\n }\n if (rendered.length > 0) {\n notes.push(\n `${rendered.length === 1 ? \"One of these addresses opens\" : `${rendered.length} of these addresses open`} a rendering of a document rather than the document: what follows the ARK name in the address, such as 'thumbnail', is the view being asked for, and 'ark' names the document that view was taken from. Read 'rendering' before quoting a link as the document itself.`,\n );\n }\n if (counts.ocr > 0) {\n notes.push(\n `${counts.ocr === 1 ? \"One of these points\" : `${counts.ocr} of these point`} at a text a machine read off a scanned document. This server names that text and does not read it.`,\n );\n }\n if (data.hasMore) {\n notes.push(\n args.limit >= 200\n ? \"The catalogue points at more documents than this tool will return in one answer, and it does not page: 200 is the ceiling. Read the editions with list_editions to reach the rest, one page at a time.\"\n : `More links exist than were returned. Raise 'limit' to see more of them, up to 200. This tool does not page, so a higher limit re-reads the links already shown rather than continuing after them.`,\n );\n notes.push(\n \"The figures under 'links_returned_by_role' count the links in this answer, so they fall short of what the catalogue attaches by however much was left out.\",\n );\n }\n if (links.length === 0) {\n notes.push(\n `The catalogue attaches no digitised document to \"${id.id}\". That is a statement about the catalogue: the BnF digitises a fraction of what it holds, and a document can be on Gallica without the record here pointing at it.`,\n );\n }\n\n const body =\n links.length === 0\n ? `The BnF catalogue attaches no digitised document to \"${id.id}\".`\n : `${links.length} link(s) for \"${id.id}\" (${kind}):\\n${links\n .map((link, index) => {\n const from = link.from_title ? ` · from: ${link.from_title}` : \"\";\n return `${index + 1}. [${link.role}] ${link.url}${from}`;\n })\n .join(\"\\n\")}`;\n\n return ok(\n {\n id: id.id,\n kind,\n links,\n links_returned_by_role: counts,\n has_more: data.hasMore,\n retrieved_at: retrievedAt,\n notes,\n },\n body,\n { retrievedAt, notes, sourceUrl: id.pageUrl },\n );\n } catch (error) {\n return toToolError(error);\n }\n}\n\nconst countRole = (links: readonly DigitisedLink[], role: DigitisedLink[\"role\"]): number =>\n links.filter((link) => link.role === role).length;\n","/**\n * How a tool's arguments are declared, and what happens to one that is not.\n *\n * An argument this server does not declare is a question it cannot answer.\n * Reading it and dropping it produces an answer computed on the defaults, which\n * a caller reads as the answer to what they asked. So an undeclared argument is\n * refused, and the refusal names it and offers the declared name when one is\n * close enough to be the one that was meant.\n *\n * A caller branches on the code a refusal opens with, and arguments are refused\n * along two paths: the code of a tool writes its own refusals, and the schema\n * refuses on its own before that code runs. Both open with the same code here,\n * so a caller reading the vocabulary finds it whichever path refused.\n */\n\nimport { z } from \"zod\";\n\n/** The code a caller branches on when the arguments cannot produce a request. */\nconst INVALID_INPUT = \"invalid_input\";\n\n/** Zod's own wording. The error code is written in front of it. */\nconst defaultMessage = z.core.locales.en().localeError;\n\n/** Declare a tool's arguments, refusing anything outside the declaration. */\nexport function strictInput<Shape extends z.ZodRawShape>(shape: Shape) {\n const declared = Object.keys(shape);\n\n const refuse: z.core.$ZodErrorMap = (issue) =>\n issue.code === \"unrecognized_keys\"\n ? unknownArgumentMessage(issue.keys, declared)\n : `[${INVALID_INPUT}] ${defaultMessage(issue)}`;\n\n for (const argument of Object.values(shape)) {\n carryTheCode(argument, refuse);\n }\n\n return z.strictObject(shape, { error: refuse });\n}\n\n/**\n * Route every refusal a declared argument can raise through one error map.\n *\n * A bound such as `min` raises its issue from the check that holds it, and the\n * issue names that check as the instance whose error map applies, so the map\n * reaches it only by being written onto the check itself. Zod offers no copy\n * that keeps a check's identity, hence the assignment. It is confined to\n * schemas built for a declaration and handed to this function, which is the\n * whole of what a tool module does with them.\n */\nfunction carryTheCode(schema: z.core.$ZodType, refuse: z.core.$ZodErrorMap): void {\n const definition = schema._zod.def as {\n error?: unknown;\n checks?: Array<{ _zod: { def: { error?: unknown } } }>;\n innerType?: z.core.$ZodType;\n };\n\n definition.error = refuse;\n for (const check of definition.checks ?? []) {\n check._zod.def.error = refuse;\n }\n\n // A default, an optional or any other wrapper answers for its own shape and\n // leaves the bounds to the type it wraps.\n if (definition.innerType) {\n carryTheCode(definition.innerType, refuse);\n }\n}\n\nfunction unknownArgumentMessage(keys: readonly string[], declared: readonly string[]): string {\n const named = keys\n .map((key) => {\n const near = nearestArgument(key, declared);\n return near ? `'${key}' (did you mean '${near}'?)` : `'${key}'`;\n })\n .join(\", \");\n\n return (\n `[${INVALID_INPUT}] Unknown ${keys.length > 1 ? \"arguments\" : \"argument\"} ${named}. ` +\n `This tool takes: ${declared.join(\", \")}.`\n );\n}\n\n/**\n * The declared name a caller most plausibly meant, when there is one.\n *\n * Three readings, ordered by how much each claims: the same name written\n * differently, a name that opens or closes the other, and a name a couple of\n * typing slips away. Anything further is left unnamed, because a suggestion\n * that misses sends a caller to an argument answering a different question.\n */\nfunction nearestArgument(key: string, declared: readonly string[]): string | undefined {\n const flatten = (name: string) => name.toLowerCase().replace(/[^a-z0-9]/g, \"\");\n const flat = flatten(key);\n if (flat.length === 0) {\n return undefined;\n }\n\n const sameName = declared.find((name) => flatten(name) === flat);\n if (sameName) {\n return sameName;\n }\n\n // Either name may be the longer one: a caller can qualify a name this tool\n // keeps plain, or shorten one it spells out.\n const overlapping = declared.find((name) => {\n const other = flatten(name);\n const [shorter, longer] = other.length < flat.length ? [other, flat] : [flat, other];\n // Two characters in common say nothing; three start to.\n return shorter.length >= 3 && (longer.startsWith(shorter) || longer.endsWith(shorter));\n });\n if (overlapping) {\n return overlapping;\n }\n\n let closest: string | undefined;\n let shortest = Number.POSITIVE_INFINITY;\n for (const name of declared) {\n const distance = editDistance(flat, flatten(name));\n if (distance < shortest) {\n shortest = distance;\n closest = name;\n }\n }\n\n // Up to a third of the name may differ. Past that the match is a guess.\n return shortest <= Math.max(1, Math.floor(flat.length / 3)) ? closest : undefined;\n}\n\n/**\n * Single-character insertions, deletions and substitutions between two words.\n *\n * One row of the matrix is kept at a time. The cell above comes from walking\n * that row, and the two other neighbours a cell needs, the one to its left and\n * the one diagonally above it, are carried forward from the step before, so\n * every value the walk uses is one it has just computed.\n */\nfunction editDistance(left: string, right: string): number {\n let previous = Array.from({ length: right.length + 1 }, (_, index) => index);\n let distance = right.length;\n\n for (let row = 1; row <= left.length; row += 1) {\n const current: number[] = [row];\n let diagonal = row - 1;\n let leftward = row;\n\n for (const [column, above] of previous.entries()) {\n if (column === 0) {\n continue;\n }\n const substitution = diagonal + (left[row - 1] === right[column - 1] ? 0 : 1);\n const cell = Math.min(substitution, above + 1, leftward + 1);\n current.push(cell);\n diagonal = above;\n leftward = cell;\n }\n\n previous = current;\n distance = leftward;\n }\n\n return distance;\n}\n","/** Schemas, error mapping and rendering shared by the tools. */\n\nimport { z } from \"zod\";\nimport { BnfError } from \"../errors.js\";\n\nconst ISO_DAY = /^\\d{4}-\\d{2}-\\d{2}$/;\nconst BRACKETED_TAIL = /\\(([^()]*)\\)\\s*$/;\nconst YEAR = /^\\d{4}$/;\n\n/**\n * The text block is what many clients render, and some render nothing else, so\n * it has to answer on its own. This ceiling is what keeps a list of editions\n * from arriving as several pages of publishers.\n */\nexport const MAX_TEXT_CHARS = 2400;\n\n/**\n * The credit every answer ends with.\n *\n * The BnF publishes these metadata under one condition, stated in one sentence:\n * they may be used freely provided their source is named and the date they were\n * retrieved is stated. Both halves are built into the answer rather than left to\n * whoever renders it, because a condition a caller has to remember is a\n * condition that will be forgotten.\n */\nexport const SOURCE_NAME = \"data.bnf.fr (Bibliothèque nationale de France)\";\n\n/** The date of retrieval, written as the calendar day it names. */\nexport function retrievalDay(retrievedAt: string): string {\n const day = retrievedAt.slice(0, 10);\n return ISO_DAY.test(day) ? day : retrievedAt;\n}\n\nexport function attribution(retrievedAt: string, sourceUrl?: string): string {\n const credit = `Source: ${SOURCE_NAME}, retrieved ${retrievalDay(retrievedAt)}`;\n return sourceUrl ? `${credit} — ${sourceUrl}` : credit;\n}\n\nexport interface ToolResult {\n // The SDK's result type carries an index signature for protocol extensions.\n [key: string]: unknown;\n content: Array<{ type: \"text\"; text: string }>;\n structuredContent?: Record<string, unknown>;\n isError?: boolean;\n}\n\n/** The moment the metadata came off data.bnf.fr, carried by every answer. */\nexport const retrievedAtSchema = z\n .string()\n .describe(\n \"When these metadata were read from data.bnf.fr, as an ISO 8601 instant. The BnF licence asks for this date to be stated wherever the metadata are shown, so repeat it alongside the source.\",\n );\n\n/** A link to a digitised document, which this server describes and never opens. */\nexport const digitisedLinkSchema = z.object({\n ark: z.string().describe(\"The Gallica ARK identifying the document.\"),\n url: z\n .string()\n .describe(\n \"The address the catalogue publishes, for a person to open. Read it beside 'rendering': an address asking for a rendering opens that view of the document rather than the document. This server does not read either.\",\n ),\n rendering: z\n .string()\n .nullable()\n .describe(\n \"The view the address asks Gallica for, read off what follows the ARK name in it, such as 'thumbnail' for a small image or 'item' for one leaf. Null when the address names the document itself. This says what was asked for, not what comes back.\",\n ),\n role: z\n .enum([\"reproduction\", \"ocr\", \"depiction\"])\n .describe(\n \"'reproduction' is the edition itself digitised. 'ocr' is the text a machine read off it. 'depiction' is an image illustrating the record, which can be a portrait or a page mentioning the subject rather than the work.\",\n ),\n from_id: z.string().describe(\"The record this link hangs off.\"),\n from_title: z.string().nullable(),\n});\n\n/**\n * Keep text from data.bnf.fr out of the shape this server's own lines take.\n *\n * This server writes three kinds of line a reader treats as its own voice:\n * `Note:`, `Source:` and `Hint:`. A cataloguer's note or a title carrying those\n * same words at the start of a line is indistinguishable from one of them, and a\n * forged `Source:` line placed above the real one substitutes a different\n * attribution for the BnF's.\n *\n * Indenting such a line keeps the two apart and costs nothing: the structured\n * output still carries the text exactly as it was published. The match ignores\n * case and allows a space before the colon, because a forgery chooses its own\n * spelling, and `m` treats a carriage return, a line feed and the Unicode line\n * and paragraph separators alike.\n */\nexport function indentMarkerLines(body: string): string {\n return body.replace(/^(Note|Source|Hint)(\\s*:)/gim, \" $1$2\");\n}\n\n/**\n * Third-party text folded onto one line, for the places that build a sentence\n * around it.\n *\n * A note and an error message are each one line by construction, and the marker\n * they open with is written by this file. A value carrying a line break would\n * put whatever follows it at the start of a line of its own, where it could\n * spell `Source:` and be read as this server's attribution. Removing the break\n * is what keeps the line count equal to one, which is what makes the marker at\n * its head the only one there is.\n */\nexport function onOneLine(value: string): string {\n // U+2028 and U+2029 are written as escapes because they terminate a line in\n // source as well as in text: written literally they would end this one.\n return value.replace(/[\\r\\n\\u2028\\u2029]+/g, \" \").trim();\n}\n\n/**\n * Build a result whose text block ends with its notes and its credit.\n *\n * The body is cut to fit around the trailer rather than the whole block being\n * cut afterwards. Appending the credit and then truncating loses exactly the\n * credit and the date beside it, which are the two lines the licence requires.\n *\n * Notes qualify an answer: that a list was cut, that a search does not rank,\n * that a record is provisional. A client that shows only the text would\n * otherwise present an unqualified answer, so they travel with the credit.\n */\nexport function ok(\n structured: Record<string, unknown>,\n body: string,\n options: { retrievedAt: string; notes?: string[]; sourceUrl?: string },\n): ToolResult {\n const credit = attribution(options.retrievedAt, options.sourceUrl);\n\n // Notes are guarded the same way the body is. Several of them are built\n // around a value the BnF published, and the trailer is appended after the\n // body has been made safe, so a note is the one way third-party text could\n // otherwise reach the block already looking like a line this server wrote.\n const noteLines = (options.notes ?? []).map((note) => `Note: ${onOneLine(note)}`);\n while (noteLines.length > 0 && noteLines.join(\"\\n\").length > MAX_TEXT_CHARS / 2) {\n noteLines.pop();\n }\n const trailer = [...noteLines, credit].join(\"\\n\");\n\n const cut = \"\\n\\n[shortened; the full result is in the structured output]\";\n const budget = MAX_TEXT_CHARS - `\\n\\n${trailer}`.length;\n const safe = indentMarkerLines(body);\n const text =\n safe.length <= budget\n ? `${safe}\\n\\n${trailer}`\n : `${truncate(safe, Math.max(0, budget - cut.length))}${cut}\\n\\n${trailer}`;\n\n return { content: [{ type: \"text\", text }], structuredContent: structured };\n}\n\n/**\n * Errors carry no structured payload: the SDK checks it against the tool's\n * declared output schema, and a failure does not fit that shape.\n */\nexport function toToolError(error: unknown): ToolResult {\n const known =\n error instanceof BnfError\n ? error\n : new BnfError(\"network_error\", error instanceof Error ? error.message : String(error));\n\n // A failure message can carry text from the endpoint and identifiers a caller\n // wrote, so it is guarded like any other body.\n const lines = [`[${known.code}] ${onOneLine(known.message)}`];\n if (known.details.hint) {\n lines.push(`Hint: ${onOneLine(known.details.hint)}`);\n }\n return { content: [{ type: \"text\", text: lines.join(\"\\n\") }], isError: true };\n}\n\nexport function truncate(text: string, maxChars: number): string {\n if (text.length <= maxChars) {\n return text;\n }\n return `${text.slice(0, Math.max(0, maxChars - 1)).trimEnd()}…`;\n}\n\n/**\n * What a caller is owed when the answer rests on a window that filled up.\n *\n * A text search reads a fixed window of the index and filters what came back,\n * and the window is read first. A page can therefore hold every row the filter\n * kept while the index holds records the reading never reached. Saying the\n * window filled is a statement about the reading; no number of matches appears,\n * because the index scores nothing and a count printed beside these rows would\n * be read as a measure of them.\n *\n * The second sentence is the one a caller needs most: the window is filled in\n * the order the index answers in, which is fixed by nothing, so two readings of\n * one search can hold different records. A window with room to spare holds\n * every match, which is what makes such an answer repeatable.\n */\nexport const WINDOW_FULL_CAVEAT =\n \"The window this search reads off the index came back full, so records carrying these words sit past what was read and this list is a part of the matches. Which records fill a full window is settled by the index as it answers, so another reading of the same search can bring back other rows. Requiring one more word narrows the reading and reaches what a full window leaves out.\";\n\n/** What a full window makes of the field that says where the rows stop. */\nexport const WINDOW_FULL_AND_NO_MORE =\n \"'has_more' is false because the rows of this window stop here, and the window came back full: it says where the reading stopped rather than that the catalogue holds nothing further under these words.\";\n\n/** Wording used wherever a work-form code reaches a caller. */\nexport const FORMS_CAVEAT =\n \"The codes under 'forms' are the terms of the BnF's work-form vocabulary a record points at, passed on as they stand: the vocabulary publishes no label for them here, so some read as words, such as 'roman' or 'poesi', and others, such as 'te', say nothing on their own. A work carrying no code is a work whose form the catalogue does not state.\";\n\n/** What the codes cannot do, said where a caller holds enough rows to try. */\nexport const FORMS_FILTER_CAVEAT =\n \"A form is stated on some works and left unsaid on others, so keeping the rows that carry one code finds the works declaring it and never all the works of that form.\";\n\n/**\n * What a caller is owed wherever one authority record is taken for a person.\n *\n * The BnF opens a heading for a name it met on a document and keeps it beside\n * the fuller record for the same person, so a record can carry a name and\n * nothing else. Every link and every field hangs off a record, which makes a\n * silence on one of them a fact about that record alone.\n */\nexport const SEVERAL_RECORDS_CAVEAT =\n \"The BnF keeps more than one authority record for some people, and a record can carry a name and nothing else while a second one carries the dates, the occupation and the works. Everything here is read off this record, so search_authors under the name will show the other records the file holds and let you compare them.\";\n\n/**\n * What a caller is owed about the reading that turned their text into terms.\n *\n * Every term is mandatory, so the reading decides what the answer can be. Three\n * things about it are invisible from the answer alone: a character that marks\n * nothing having been removed, punctuation having reached no term, and a term\n * of one character being required like any other. Each of them can be what\n * emptied a list, and none of them shows in the rows.\n */\nexport function readingNotes(\n reading: { terms: string[]; invisible: string[]; setAside: string[] },\n field: \"name\" | \"title\",\n): string[] {\n const notes: string[] = [];\n const quoted = (values: readonly string[]): string =>\n values.map((value) => `\"${value}\"`).join(\", \");\n\n if (reading.invisible.length > 0) {\n notes.push(\n `The text held ${reading.invisible.length} character(s) that mark nothing on a screen, such as a control character or a zero-width space. They were removed before the words were cut, so a word one of them sat inside was searched for whole rather than as two terms the index would each require.`,\n );\n }\n if (reading.setAside.length > 0) {\n notes.push(\n `Only letters and digits reach the index: ${quoted(reading.setAside)} in the text ${reading.setAside.length === 1 ? \"was\" : \"were\"} set aside, and what was searched for is ${quoted(reading.terms)}.`,\n );\n }\n\n const single = reading.terms.filter((term) => [...term].length === 1);\n if (single.length > 0) {\n notes.push(\n `${quoted(single)} ${single.length === 1 ? \"is a term of one character\" : \"are terms of one character\"} and ${single.length === 1 ? \"is\" : \"are\"} required like any other: a record has to carry ${single.length === 1 ? \"it\" : \"them\"} in its ${field} for a row to be a match. Leaving ${single.length === 1 ? \"it\" : \"them\"} out widens the search.`,\n );\n }\n return notes;\n}\n\n/** Wording used wherever a search that does not rank reaches a caller. */\nexport const NO_RANKING =\n \"The full-text index answers whether a record matches, and it does not score how well. Rows are ordered by the address of the record, which measures nothing, so the row a person would call the obvious answer can sit anywhere in the list, or on a later page. That order is the one the pages are cut along, so reading page after page reaches every match once.\";\n\n/**\n * What a page holding no row turned out to be.\n *\n * 'absent' is the words matching no record at all. 'past_the_end' is a search\n * that matches, whose rows stop before the page asked for. 'undetermined' is\n * the reading that would tell them apart having failed.\n */\nexport type EmptyPage = \"absent\" | \"past_the_end\" | \"undetermined\";\n\n/**\n * Tell a page asked for beyond the last row from a search that matches nothing.\n *\n * Both come back as a page of no rows, and calling either one an absence states\n * something the reading did not establish. Since a search reports no total, the\n * only way to separate them is to read the search from its first row: a row\n * there means the words match and the page asked for sits past where the rows\n * stop. The first page is its own evidence and is never read twice.\n */\nexport async function classifyEmptyPage(\n page: number,\n firstPageHasRow: () => Promise<boolean>,\n): Promise<EmptyPage> {\n if (page === 1) {\n return \"absent\";\n }\n try {\n return (await firstPageHasRow()) ? \"past_the_end\" : \"absent\";\n } catch {\n return \"undetermined\";\n }\n}\n\n/** The class data.bnf.fr types a person with. */\nexport const PERSON_TYPE = \"http://xmlns.com/foaf/0.1/Person\";\n\n/** The classes data.bnf.fr types a work with, one per vocabulary it uses. */\nexport const WORK_TYPES: readonly string[] = [\n \"http://rdvocab.info/uri/schema/FRBRentitiesRDA/Work\",\n \"http://rdaregistry.info/Elements/c/#C10001\",\n];\n\n/**\n * What the catalogue says an address is.\n *\n * An edition, an expression and a subject heading carry identifiers of the same\n * shape as a person and a work, and a tool that reads one of them as the other\n * asks a question it can never answer. The empty answer that comes back is\n * indistinguishable from the BnF holding nothing, so the type is read and null\n * is returned for everything else, which the caller refuses by name.\n */\nexport function recordKindOf(types: readonly string[]): \"person\" | \"work\" | null {\n if (types.includes(PERSON_TYPE)) {\n return \"person\";\n }\n if (types.some((type) => WORK_TYPES.includes(type))) {\n return \"work\";\n }\n return null;\n}\n\n/** The years a BnF heading states in its brackets, read off the end of it. */\nfunction headingYears(label: string): { birth: number | null; death: number | null } {\n const bracketed = BRACKETED_TAIL.exec(label);\n const [birth, death] = (bracketed?.[1] ?? \"\").split(\"-\");\n const year = (part: string | undefined): number | null =>\n part !== undefined && YEAR.test(part.trim()) ? Number(part.trim()) : null;\n return { birth: year(birth), death: year(death) };\n}\n\n/**\n * Where a heading and the dated fields of the same record disagree.\n *\n * A BnF heading writes the dates in brackets and the record states them again\n * as numbers, and a correction applied to one side and not the other leaves the\n * record saying two things. The numbers are what a caller compares to tell two\n * people of one name apart, so a disagreement is reported: this server has no\n * way of knowing which side a cataloguer meant, and picking one would hand over\n * a date the record does not settle.\n */\nexport function headingYearConflicts(\n label: string | null,\n birthYear: number | null,\n deathYear: number | null,\n): string[] {\n if (label === null) {\n return [];\n }\n const stated = headingYears(label);\n const conflicts: string[] = [];\n if (stated.birth !== null && birthYear !== null && stated.birth !== birthYear) {\n conflicts.push(`${stated.birth} against 'birth_year' ${birthYear}`);\n }\n if (stated.death !== null && deathYear !== null && stated.death !== deathYear) {\n conflicts.push(`${stated.death} against 'death_year' ${deathYear}`);\n }\n return conflicts;\n}\n\n/** Wording used wherever a Gallica link reaches a caller. */\nexport const GALLICA_CAVEAT =\n \"These are links for a person to open. This server reads the BnF catalogue and never requests gallica.bnf.fr, so it cannot say whether a document opens, what it contains, or on what terms it may be reused.\";\n\n/**\n * Wording used wherever the addresses under 'same_as' reach a caller.\n *\n * They are the BnF's own alignments, carried through character for character:\n * an address is a key, and re-spelling one, by decoding an escape or by adding\n * an escape of this server's own, produces an address the catalogue never\n * published which can name another page or none. This server requests\n * data.bnf.fr and nothing else, so whether any of them answers today is\n * something it has not looked at and does not claim.\n */\nexport const ALIGNMENTS_CAVEAT =\n \"The addresses under 'same_as' are the BnF's own alignments to other files, passed on exactly as the catalogue publishes them, character for character. This server requests data.bnf.fr alone and does not open them, so it cannot say whether one still answers or what stands at the other end.\";\n\n/** Wording used wherever a provisional record reaches a caller. */\nexport const PROVISIONAL_CAVEAT =\n \"A provisional record is one the BnF created to hold a title it has catalogued and not yet established as a work of its own. Its identifier can change, and it can be merged into an established record.\";\n","/**\n * get_author: read one person's authority record.\n *\n * The record is a set of statements a cataloguer entered, and its shape says\n * what it is for. Dates, places, a language, a country, a Dewey class and\n * alignments to VIAF, IdRef and DBpedia are all there and all reliable. A\n * biography is not: `biographicalInformation` holds \"Poète\" on Rimbaud, and a\n * caller asked for a life story has to be told that the field is a job title\n * rather than handed one word dressed up as one.\n */\n\nimport { z } from \"zod\";\nimport type { BnfClient } from \"../bnf/client.js\";\nimport { parseEntityId } from \"../bnf/sparql.js\";\nimport { strictInput } from \"./arguments.js\";\nimport {\n ALIGNMENTS_CAVEAT,\n GALLICA_CAVEAT,\n SEVERAL_RECORDS_CAVEAT,\n digitisedLinkSchema,\n headingYearConflicts,\n ok,\n retrievedAtSchema,\n toToolError,\n} from \"./shared.js\";\nimport type { ToolResult } from \"./shared.js\";\nimport type { AuthorDetail } from \"../types.js\";\n\n/**\n * What the catalogue's own shape says about the record, beyond its fields.\n *\n * Each sentence answers something a reader would otherwise conclude wrongly: a\n * field that holds an occupation rather than a life, one statement written\n * twice, dates that disagree with the heading they sit under, and a silence\n * about a death that means different things depending on whether the record\n * dates the person at all.\n */\nfunction notesOnWhatTheRecordStates(data: AuthorDetail, args: GetAuthorArgs): string[] {\n const notes: string[] = [];\n\n if (data.biographicalInformation !== null) {\n notes.push(\n `The BnF states \"${data.biographicalInformation}\" as this person's biographical information. That field holds an occupation rather than a life, and this is all the catalogue says.`,\n );\n }\n // Two fields carrying one string are one statement. Read as two they\n // corroborate each other, and a reader counts a second source that the\n // record does not hold.\n if (data.biographicalInformation !== null && data.biographicalInformation === data.occupation) {\n notes.push(\n \"This record states the same text under 'biographical_information' and 'occupation', character for character. It is one statement written in two fields rather than two the record makes separately.\",\n );\n }\n // A record can carry its dates twice, in the brackets of a heading and in\n // the fields, and the two can disagree. The fields are what the searches\n // read and what a caller compares to tell two people of one name apart, so\n // the disagreement is stated: nothing on the record says which side a\n // cataloguer meant, and choosing one would settle a date the BnF has not.\n const headings = [data.label, ...data.otherNames.map((other) => other.label)];\n const conflicts: string[] = [\n ...new Set(\n headings.flatMap((heading) => headingYearConflicts(heading, data.birthYear, data.deathYear)),\n ),\n ];\n if (conflicts.length > 0) {\n notes.push(\n `This record states its dates twice and the two disagree: the heading says ${conflicts.join(\", and \")}. Both are passed on as the BnF publishes them, and the record says nothing about which is right, so read source_url before citing either.`,\n );\n }\n\n // What the silence about a death is worth depends on whether the record\n // dates the person at all. A record carrying a birth places them in time\n // and leaves one statement missing; a record carrying neither places them\n // nowhere, and reading a life out of it is reading a life out of a heading.\n if (data.deathDate === null && data.deathYear === null) {\n const dated = data.birthDate !== null || data.birthYear !== null;\n notes.push(\n dated\n ? \"The record states no date of death. That can mean the person is living, or that the BnF has not recorded one; the catalogue does not distinguish the two.\"\n : `This record states no date of birth and no date of death, so it dates the person nowhere and says nothing about whether they are living. ${SEVERAL_RECORDS_CAVEAT}`,\n );\n }\n if (Object.keys(data.sameAs).length > 0) {\n notes.push(\n `${ALIGNMENTS_CAVEAT} Following one is how to reach a biography, which this catalogue does not hold.`,\n );\n }\n // The caveat is about links a caller is holding. On an answer that returns\n // none, it describes a list that is not there and reads as a list withheld.\n if (args.include_depictions && data.depictions.length > 0) {\n notes.push(GALLICA_CAVEAT);\n }\n\n return notes;\n}\n\nexport const getAuthorDescription = [\n \"Read one person's record in the Bibliothèque nationale de France authority file, by the identifier search_authors returns.\",\n \"It carries the dates and places of birth and death, the occupation the record names, the language and country it associates with the person, the field of activity with its Dewey class, and the addresses for the same person in VIAF, IdRef, DBpedia, Wikidata and ISNI.\",\n \"It carries no biography. The field the BnF calls biographical information is a job title on most records and a single word on many, so quote it as what the catalogue states and do not build a life story out of it.\",\n \"It lists no work. list_works walks from this record to the works the catalogue names the person the creator of, and states what that link does and does not reach.\",\n \"'depictions' are images data.bnf.fr attaches to the person. They are links for a person to open; this server does not read them, and an image can be a page that mentions the person rather than a portrait of them.\",\n].join(\" \");\n\nexport const getAuthorInput = strictInput({\n author_id: z\n .string()\n .min(1)\n .max(200)\n .describe(\n \"The identifier from search_authors, such as 'cb119219976'. A full data.bnf.fr address is accepted too.\",\n ),\n include_depictions: z\n .boolean()\n .default(false)\n .describe(\n \"Include the images data.bnf.fr attaches to the person. A well-known person can carry dozens, so they are left out by default. find_digitised gathers them with everything else.\",\n ),\n});\n\nexport const getAuthorOutput = z.object({\n author: z.object({\n id: z.string(),\n name: z.string().nullable(),\n label: z.string().nullable().describe(\"The authority heading, usually with the dates.\"),\n given_name: z.string().nullable(),\n family_name: z.string().nullable(),\n other_names: z\n .array(z.object({ label: z.string(), language: z.string().nullable() }))\n .describe(\"Other headings the record carries, including transliterations and pen names.\"),\n birth_date: z.string().nullable().describe(\"As published, usually YYYY-MM-DD.\"),\n death_date: z.string().nullable(),\n birth_year: z.number().int().nullable(),\n death_year: z.number().int().nullable(),\n birth_place: z.string().nullable(),\n death_place: z.string().nullable(),\n biographical_information: z\n .string()\n .nullable()\n .describe(\n \"What the record states about the person. This is a job title on most records and a single word on many; it is not a biography.\",\n ),\n occupation: z.string().nullable(),\n languages: z.array(z.string()).describe(\"ISO 639-2 codes, such as 'fre'.\"),\n countries: z.array(z.string()).describe(\"Country codes, such as 'fr'.\"),\n fields: z.array(z.string()).describe(\"Fields of activity in the words of the record.\"),\n dewey_classes: z\n .array(z.string())\n .describe(\"Dewey classes the fields point at, such as '800' for literature.\"),\n same_as: z\n .record(z.string(), z.array(z.string()))\n .describe(\"Addresses for the same person in other files, grouped by file.\"),\n catalogue_url: z\n .string()\n .nullable()\n .describe(\n \"The address of this record in the BnF general catalogue, as the record itself points at it. Null when the record states none, which is a silence in the record rather than a record the general catalogue lacks: no address is built in its place.\",\n ),\n record_created: z.string().nullable().describe(\"When the authority record was created.\"),\n record_modified: z.string().nullable().describe(\"When it was last changed.\"),\n source_url: z.string(),\n }),\n depictions: z.array(digitisedLinkSchema).optional(),\n depiction_count: z\n .number()\n .int()\n .describe(\n \"Images on Gallica that the record points at, counted whether or not they were returned. An illustration the record holds elsewhere is not counted, because this server describes Gallica documents and knows nothing about the others.\",\n ),\n retrieved_at: retrievedAtSchema,\n notes: z.array(z.string()),\n});\n\nexport type GetAuthorArgs = z.infer<typeof getAuthorInput>;\n\nexport async function runGetAuthor(client: BnfClient, args: GetAuthorArgs): Promise<ToolResult> {\n try {\n const id = parseEntityId(args.author_id);\n const { data, cached, retrievedAt } = await client.getAuthor(id);\n\n const notes: string[] = [];\n if (cached) {\n notes.push(\"Served from this server's short-lived in-memory cache.\");\n }\n\n notes.push(...notesOnWhatTheRecordStates(data, args));\n\n const structured: Record<string, unknown> = {\n author: {\n id: data.id,\n name: data.name,\n label: data.label,\n given_name: data.givenName,\n family_name: data.familyName,\n other_names: data.otherNames,\n birth_date: data.birthDate,\n death_date: data.deathDate,\n birth_year: data.birthYear,\n death_year: data.deathYear,\n birth_place: data.birthPlace,\n death_place: data.deathPlace,\n biographical_information: data.biographicalInformation,\n occupation: data.occupation,\n languages: data.languages,\n countries: data.countries,\n fields: data.fields,\n dewey_classes: data.deweyClasses,\n same_as: data.sameAs,\n catalogue_url: data.catalogueUrl,\n record_created: data.recordCreated,\n record_modified: data.recordModified,\n source_url: data.sourceUrl,\n },\n depiction_count: data.depictions.length,\n retrieved_at: retrievedAt,\n notes,\n };\n\n if (args.include_depictions) {\n structured.depictions = data.depictions.map((link) => ({\n ark: link.ark,\n url: link.url,\n rendering: link.rendering,\n role: link.role,\n from_id: link.fromId,\n from_title: link.fromTitle,\n }));\n }\n\n const born = [\n data.birthDate ?? (data.birthYear === null ? null : String(data.birthYear)),\n data.birthPlace,\n ]\n .filter(Boolean)\n .join(\", \");\n const died = [\n data.deathDate ?? (data.deathYear === null ? null : String(data.deathYear)),\n data.deathPlace,\n ]\n .filter(Boolean)\n .join(\", \");\n\n const lines = [\n data.label ?? data.name ?? data.id,\n born ? `Born: ${born}` : \"\",\n died ? `Died: ${died}` : \"\",\n data.occupation ? `Occupation as recorded: ${data.occupation}` : \"\",\n data.fields.length > 0\n ? `Field of activity: ${data.fields.join(\", \")}${data.deweyClasses.length > 0 ? ` (Dewey ${data.deweyClasses.join(\", \")})` : \"\"}`\n : \"\",\n data.languages.length > 0 ? `Language: ${data.languages.join(\", \")}` : \"\",\n data.countries.length > 0 ? `Country: ${data.countries.join(\", \")}` : \"\",\n data.otherNames.length > 0\n ? `Also recorded as: ${data.otherNames.map((other) => other.label).join(\"; \")}`\n : \"\",\n Object.keys(data.sameAs).length > 0\n ? `Same person in: ${Object.entries(data.sameAs)\n .map(([file, addresses]) => `${file} ${addresses[0]}`)\n .join(\" · \")}`\n : \"\",\n data.catalogueUrl ? `BnF catalogue: ${data.catalogueUrl}` : \"\",\n data.depictions.length > 0\n ? `Images attached to this record: ${data.depictions.length}${args.include_depictions ? \"\" : \" (ask with include_depictions, or use find_digitised)\"}`\n : \"\",\n ].filter(Boolean);\n\n return ok(structured, lines.join(\"\\n\"), { retrievedAt, notes, sourceUrl: data.sourceUrl });\n } catch (error) {\n return toToolError(error);\n }\n}\n","/**\n * get_work: read one work's record.\n *\n * The BnF holds two kinds of work record, and telling them apart is the point\n * of this tool. An established work has an ARK of its own, a settled title, and\n * links to every expression the catalogue knows. A provisional one is addressed\n * under `temp-work`, was created to hold a title a cataloguer has not yet\n * settled, and can be renamed, re-addressed or merged away. Quoting a\n * provisional identifier as a stable one is how a citation goes bad quietly, so\n * the record says which it is in words rather than leaving it to be read off\n * the shape of an address.\n */\n\nimport { z } from \"zod\";\nimport type { BnfClient } from \"../bnf/client.js\";\nimport { parseEntityId } from \"../bnf/sparql.js\";\nimport { strictInput } from \"./arguments.js\";\nimport {\n ALIGNMENTS_CAVEAT,\n FORMS_CAVEAT,\n GALLICA_CAVEAT,\n PROVISIONAL_CAVEAT,\n digitisedLinkSchema,\n ok,\n retrievedAtSchema,\n toToolError,\n} from \"./shared.js\";\nimport type { ToolResult } from \"./shared.js\";\nimport type { WorkDetail } from \"../types.js\";\n\n/**\n * What the catalogue's own shape says about the work, beyond its fields.\n *\n * A provisional record, a form stated as a code the catalogue publishes no\n * label for, an alignment that reaches a description this catalogue does not\n * hold: each is something a reader would otherwise take for more than it is.\n */\nfunction notesOnWhatTheRecordStates(data: WorkDetail, args: GetWorkArgs): string[] {\n const notes: string[] = [];\n\n if (data.status === \"provisional\") {\n notes.push(\n `This is a provisional record, addressed under 'temp-work' rather than by an ARK${data.statusStatement ? ` and stated as \"${data.statusStatement}\"` : \"\"}. ${PROVISIONAL_CAVEAT}`,\n );\n } else {\n notes.push(\n `This is an established work record, addressed by the ARK '${data.id}'${data.statusStatement ? ` and stated as \"${data.statusStatement}\"` : \"\"}. That identifier is the one to cite.`,\n );\n }\n\n if (data.truncated) {\n notes.push(\n \"This record is longer than one query reads, so parts of it are missing from this answer. A field shown as empty here may be filled on the record itself: open source_url before concluding that the BnF states nothing.\",\n );\n }\n if (data.expressionCount !== null && data.expressionCount > 0) {\n notes.push(\n `The record links ${data.expressionCount} expression(s): translations, adaptations and recordings of this work. Published editions are a separate count, and list_editions is what reads them.`,\n );\n }\n // The codes are printed in the body and in the payload, so what they are\n // travels with them: a reader shown 'poesi' beside 'te' has no way of\n // telling a word from an opaque term without being told.\n if (data.forms.length > 0) {\n notes.push(FORMS_CAVEAT);\n }\n if (data.creators.length === 0) {\n notes.push(\n \"The record credits nobody with this work. That is normal for anonymous and traditional works, and it also happens on records a cataloguer has not finished.\",\n );\n }\n if (Object.keys(data.sameAs).length > 0) {\n notes.push(ALIGNMENTS_CAVEAT);\n }\n // The caveat is about links a caller is holding. On an answer that returns\n // none, it describes a list that is not there and reads as a list withheld.\n if (args.include_depictions && data.depictions.length > 0) {\n notes.push(GALLICA_CAVEAT);\n }\n\n return notes;\n}\n\nexport const getWorkDescription = [\n \"Read one work's record in the Bibliothèque nationale de France catalogue, by the identifier search_works returns.\",\n \"It carries the title, everyone the record credits, the date the BnF gives the work, its language, its form and its subject with the Dewey class.\",\n \"'forms' holds the work-form vocabulary's own terms, which carry no label in this dataset, so some read as words and some do not. A record stating none has a form the catalogue does not state.\",\n \"'status' says whether the record is established or provisional, and 'status_statement' repeats what the catalogue itself states. A provisional identifier can change, so cite an established one where there is a choice.\",\n \"'expression_count' counts the expressions the record links, which is not a count of published editions: use list_editions for those.\",\n].join(\" \");\n\nexport const getWorkInput = strictInput({\n work_id: z\n .string()\n .min(1)\n .max(200)\n .describe(\n \"The identifier from search_works, such as 'cb11970626n' or 'temp-work/22d7f68c1a4bdd081ad7ca791fd3b730'.\",\n ),\n include_depictions: z\n .boolean()\n .default(false)\n .describe(\n \"Include the images data.bnf.fr attaches to the work. A well-known work can carry dozens, so they are left out by default. find_digitised gathers them alongside the digitised editions.\",\n ),\n});\n\nexport const getWorkOutput = z.object({\n work: z.object({\n id: z.string(),\n title: z.string().nullable(),\n label: z.string().nullable(),\n date: z.string().nullable().describe(\"The date the record gives the work, as published.\"),\n first_year: z.number().int().nullable(),\n creators: z.array(z.object({ id: z.string(), name: z.string().nullable() })),\n languages: z.array(z.string()).describe(\"ISO 639-2 codes.\"),\n forms: z\n .array(z.string())\n .describe(\n \"Terms of the BnF work-form vocabulary, as the terms themselves. The vocabulary publishes no label for them here, so they are not translated: some read as words, such as 'poesi', and some do not, such as 'te'. An empty list is a form the record does not state.\",\n ),\n subjects: z.array(z.string()).describe(\"Subjects in the words of the record.\"),\n dewey_classes: z.array(z.string()),\n status: z.enum([\"established\", \"provisional\"]),\n status_statement: z\n .string()\n .nullable()\n .describe(\"What the record itself states, such as 'fully established' or 'provisional'.\"),\n expression_count: z\n .number()\n .int()\n .nullable()\n .describe(\n \"Expressions the record links: translations, adaptations, recordings. This is not a count of published editions. Null when the record was longer than one query reads, since the number would then be the ceiling rather than the count.\",\n ),\n same_as: z.record(z.string(), z.array(z.string())),\n catalogue_url: z\n .string()\n .nullable()\n .describe(\n \"The address of this record in the BnF general catalogue, as the record itself points at it. Null when the record states none, which is the ordinary case on work records: it is a silence in the record rather than a record the general catalogue lacks, and no address is built in its place.\",\n ),\n source_url: z.string(),\n }),\n depictions: z.array(digitisedLinkSchema).optional(),\n depiction_count: z\n .number()\n .int()\n .describe(\n \"Images on Gallica that the record points at, counted whether or not they were returned.\",\n ),\n retrieved_at: retrievedAtSchema,\n notes: z.array(z.string()),\n});\n\nexport type GetWorkArgs = z.infer<typeof getWorkInput>;\n\nexport async function runGetWork(client: BnfClient, args: GetWorkArgs): Promise<ToolResult> {\n try {\n const id = parseEntityId(args.work_id);\n const { data, cached, retrievedAt } = await client.getWork(id);\n\n const notes: string[] = [];\n if (cached) {\n notes.push(\"Served from this server's short-lived in-memory cache.\");\n }\n\n notes.push(...notesOnWhatTheRecordStates(data, args));\n\n const structured: Record<string, unknown> = {\n work: {\n id: data.id,\n title: data.title,\n label: data.label,\n date: data.date,\n first_year: data.firstYear,\n creators: data.creators,\n languages: data.languages,\n forms: data.forms,\n subjects: data.subjects,\n dewey_classes: data.deweyClasses,\n status: data.status,\n status_statement: data.statusStatement,\n expression_count: data.expressionCount,\n same_as: data.sameAs,\n catalogue_url: data.catalogueUrl,\n source_url: data.sourceUrl,\n },\n depiction_count: data.depictions.length,\n retrieved_at: retrievedAt,\n notes,\n };\n\n if (args.include_depictions) {\n structured.depictions = data.depictions.map((link) => ({\n ark: link.ark,\n url: link.url,\n rendering: link.rendering,\n role: link.role,\n from_id: link.fromId,\n from_title: link.fromTitle,\n }));\n }\n\n const lines = [\n [data.title ?? data.label ?? data.id, data.date ? `(${data.date})` : \"\"]\n .filter(Boolean)\n .join(\" \"),\n data.creators.length > 0\n ? `By: ${data.creators.map((creator) => creator.name ?? creator.id).join(\", \")}`\n : \"\",\n `Record: ${data.status === \"provisional\" ? \"provisional\" : \"established\"}`,\n data.languages.length > 0 ? `Language: ${data.languages.join(\", \")}` : \"\",\n data.forms.length > 0 ? `Form: ${data.forms.join(\", \")}` : \"\",\n data.subjects.length > 0\n ? `Subject: ${data.subjects.join(\", \")}${data.deweyClasses.length > 0 ? ` (Dewey ${data.deweyClasses.join(\", \")})` : \"\"}`\n : \"\",\n data.catalogueUrl ? `BnF catalogue: ${data.catalogueUrl}` : \"\",\n ].filter(Boolean);\n\n return ok(structured, lines.join(\"\\n\"), { retrievedAt, notes, sourceUrl: data.sourceUrl });\n } catch (error) {\n return toToolError(error);\n }\n}\n","/**\n * list_editions: the published editions of one work.\n *\n * A manifestation is what a reader would call an edition: a publisher, a place,\n * a year, an extent, and sometimes a digitised copy. What the catalogue does\n * not record is whether that copy can be opened, by whom, or under what terms,\n * and there is no field that would let this server work it out. So a row that\n * carries a Gallica link says a link exists, and stops there.\n */\n\nimport { z } from \"zod\";\nimport type { BnfClient } from \"../bnf/client.js\";\nimport type { EntityId } from \"../bnf/sparql.js\";\nimport { notFound } from \"../errors.js\";\nimport { parseEntityId } from \"../bnf/sparql.js\";\nimport { strictInput } from \"./arguments.js\";\nimport {\n GALLICA_CAVEAT,\n digitisedLinkSchema,\n ok,\n recordKindOf,\n retrievedAtSchema,\n toToolError,\n} from \"./shared.js\";\nimport type { ToolResult } from \"./shared.js\";\n\n/**\n * Why a page of editions came back empty.\n *\n * A page past the last one and a work the catalogue links no edition to are\n * different statements about the catalogue, and a caller cannot act on the\n * first the way they would act on the second.\n */\nfunction nothingOnThisPage(page: number, work: string): string {\n if (page > 1) {\n return `Page ${page} of the editions of \"${work}\" holds no row.`;\n }\n return `The BnF catalogue links no edition to the work \"${work}\".`;\n}\n\nexport const listEditionsDescription = [\n \"List the published editions of one work in the Bibliothèque nationale de France catalogue, by the identifier search_works or get_work returns.\",\n \"Each row carries the publisher, the place, the year, the edition statement, the extent in the words of the record, the ISBN when there is one, and the record in the BnF general catalogue.\",\n \"An edition that has been digitised carries a link under 'digitised'. That link says a copy exists at that address; this server never opens it, so it cannot say whether the document is readable, complete, or free to reuse.\",\n \"Rows are ordered by the address the catalogue gives each edition, which is neither by date nor by importance.\",\n].join(\" \");\n\nexport const listEditionsInput = strictInput({\n work_id: z.string().min(1).max(200).describe(\"The work identifier, such as 'cb11970626n'.\"),\n limit: z.number().int().min(1).max(50).default(10),\n page: z.number().int().min(1).max(100).default(1),\n});\n\nexport const editionRowSchema = z.object({\n id: z.string().describe(\"The edition's own identifier in the BnF catalogue.\"),\n title: z.string().nullable().describe(\"The title as this edition carries it.\"),\n date: z\n .string()\n .nullable()\n .describe(\"The date as published, which can be a phrase such as '[s.d.]' for an undated one.\"),\n year: z.number().int().nullable().describe(\"The year, when the record states one as a number.\"),\n publisher: z\n .string()\n .nullable()\n .describe(\n \"The publisher as the record writes it, square brackets included. A bracketed part is the cataloguer speaking rather than the item: '[s.n.]' is a publisher the item does not name, and a role such as '[éd., distrib.]' is what the named house did rather than part of its name.\",\n ),\n place: z\n .string()\n .nullable()\n .describe(\n \"The place of publication as the record writes it, square brackets included. '[Paris]' is a place the cataloguer supplied because the item does not print one, and '[S.l.]' is an item stating no place at all.\",\n ),\n edition_statement: z.string().nullable().describe(\"Such as a numbered or revised edition.\"),\n extent: z\n .string()\n .nullable()\n .describe(\"The extent in the words of the record: pages, volumes, discs.\"),\n isbn: z.string().nullable(),\n note: z.string().nullable().describe(\"What the cataloguer wrote, as published.\"),\n catalogue_url: z.string().nullable(),\n digitised: z\n .array(digitisedLinkSchema)\n .describe(\n \"Digitised copies of this edition, as links. An empty list means the catalogue records none here, not that none exists.\",\n ),\n source_url: z.string(),\n});\n\nexport const listEditionsOutput = z.object({\n work_id: z.string(),\n editions: z.array(editionRowSchema),\n page: z.number().int(),\n has_more: z.boolean(),\n digitised_count: z.number().int().describe(\"Editions on this page carrying at least one link.\"),\n retrieved_at: retrievedAtSchema,\n notes: z.array(z.string()),\n});\n\nexport type ListEditionsArgs = z.infer<typeof listEditionsInput>;\n\nexport async function runListEditions(\n client: BnfClient,\n args: ListEditionsArgs,\n): Promise<ToolResult> {\n try {\n const id = parseEntityId(args.work_id);\n const offset = (args.page - 1) * args.limit;\n const { data, cached, retrievedAt } = await client.listEditions(id, args.limit, offset);\n\n const notes: string[] = [];\n if (cached) {\n notes.push(\"Served from this server's short-lived in-memory cache.\");\n }\n\n const editions = data.rows.map((row) => ({\n id: row.id,\n title: row.title,\n date: row.date,\n year: row.year,\n publisher: row.publisher,\n place: row.place,\n edition_statement: row.editionStatement,\n extent: row.extent,\n isbn: row.isbn,\n note: row.note,\n catalogue_url: row.catalogueUrl,\n digitised: row.digitised.map((link) => ({\n ark: link.ark,\n url: link.url,\n rendering: link.rendering,\n role: link.role,\n from_id: link.fromId,\n from_title: link.fromTitle,\n })),\n source_url: row.sourceUrl,\n }));\n\n const digitisedCount = editions.filter((edition) => edition.digitised.length > 0).length;\n if (digitisedCount > 0) {\n notes.push(GALLICA_CAVEAT);\n }\n\n // A bracketed value is a cataloguing convention, and a caller building a\n // citation out of one copies the cataloguer's aside into it as if the item\n // carried it. The values are passed on as published, so what the brackets\n // do is said rather than removed.\n const bracketed = editions.some(\n (edition) =>\n (edition.publisher?.includes(\"[\") ?? false) || (edition.place?.includes(\"[\") ?? false),\n );\n if (bracketed) {\n notes.push(\n \"Square brackets in 'place' and 'publisher' are the cataloguer's own, kept as the record writes them: '[S.l.]' and '[s.n.]' are an item naming no place and no publisher, a bracketed name is one supplied from outside the item, and a bracketed role such as '[éd., distrib.]' says what a named house did. Drop them before quoting a value as what the item prints.\",\n );\n }\n\n if (data.hasMore) {\n notes.push(`More editions exist beyond this page. Ask for page ${args.page + 1}.`);\n }\n if (editions.length === 0 && args.page > 1) {\n notes.push(\n `Page ${args.page} holds no row, which means the list ended earlier rather than that the work has no editions. Ask for page 1 to see it from the start.`,\n );\n } else if (editions.length === 0) {\n // A person, a subject heading and an address the BnF describes nowhere\n // all answer with no editions, and so does a work the catalogue links\n // none to. Explaining the empty list as a property of a work record\n // before the address is known to name one turns a wrong identifier into\n // a bibliographic fact.\n await assertWork(client, id);\n notes.push(\n `The catalogue links no published edition to \"${id.id}\". A work record can exist with none: it happens on provisional records, and on works the BnF describes through an anthology rather than through an edition of their own. Check get_work to see what kind of record this is.`,\n );\n }\n if (editions.length > 0) {\n notes.push(\n \"Rows are ordered by the address the catalogue gives each edition. It is neither chronological nor an order of importance, so a first edition can appear anywhere in the list. That order is the one the pages are cut along, so reading page after page reaches every edition once.\",\n );\n }\n\n const body =\n editions.length === 0\n ? nothingOnThisPage(args.page, id.id)\n : `${editions.length} edition(s) of \"${id.id}\":\\n${editions\n .map((edition, index) => {\n const imprint = [edition.place, edition.publisher, edition.date]\n .filter(Boolean)\n .join(\" : \");\n const extra = [\n edition.edition_statement,\n edition.extent,\n edition.isbn ? `ISBN ${edition.isbn}` : \"\",\n ]\n .filter(Boolean)\n .join(\" · \");\n const links =\n edition.digitised.length > 0\n ? `\\n digitised: ${edition.digitised.map((link) => `${link.role} ${link.url}`).join(\" \")}`\n : \"\";\n return `${index + 1}. ${edition.title ?? edition.id}${imprint ? ` · ${imprint}` : \"\"}${extra ? `\\n ${extra}` : \"\"}\\n ${edition.catalogue_url ?? edition.source_url}${links}`;\n })\n .join(\"\\n\")}`;\n\n return ok(\n {\n work_id: id.id,\n editions,\n page: args.page,\n has_more: data.hasMore,\n digitised_count: digitisedCount,\n retrieved_at: retrievedAt,\n notes,\n },\n body,\n { retrievedAt, notes },\n );\n } catch (error) {\n return toToolError(error);\n }\n}\n\n/** Refuse an address the catalogue describes as anything other than a work. */\nasync function assertWork(client: BnfClient, id: EntityId): Promise<void> {\n // Only a work is addressed under temp-work, so the address settles it and no\n // query is spent.\n if (id.kind === \"temp-work\") {\n return;\n }\n\n const types = await client.types(id);\n if (types.data.length === 0) {\n throw notFound(`data.bnf.fr describes nothing at \"${id.id}\".`, {\n hint: \"Find the identifier with search_works, or with list_works for the works of one person.\",\n url: id.pageUrl,\n });\n }\n if (recordKindOf(types.data) !== \"work\") {\n throw notFound(\n `\"${id.id}\" is described by data.bnf.fr, and it is not a work: it is typed ${types.data.join(\", \")}.`,\n {\n hint: \"This tool reads the editions of a work. For a person, list_works reaches the works they are credited with and get_author reads their record.\",\n url: id.pageUrl,\n },\n );\n }\n}\n","/**\n * list_works: the works the BnF catalogue names one person the creator of.\n *\n * The catalogue holds that link, so the question \"what did this person write\"\n * has an answer here. What it does not hold is a bibliography. A person is\n * credited on records in ways this link does not carry, and the BnF holds\n * printed editions whose work it has never established as an entry of its own,\n * so the list is what the link reaches and the answer says so.\n *\n * Two further things the catalogue withholds shape the rest. It points a work\n * at a term of its work-form vocabulary and publishes no label for that term\n * here, so the codes travel as they stand; and it states the form of some works\n * and not of others, which is what stops a code from being a filter: rows\n * carrying it are works declaring that form, and the rows without it are silent\n * rather than negative. And it orders nothing: the rows come back by the\n * address of the work, which is stable enough to page through and means nothing\n * else.\n */\n\nimport { z } from \"zod\";\nimport type { BnfClient } from \"../bnf/client.js\";\nimport type { EntityId } from \"../bnf/sparql.js\";\nimport { notFound } from \"../errors.js\";\nimport { parseEntityId } from \"../bnf/sparql.js\";\nimport { strictInput } from \"./arguments.js\";\nimport {\n FORMS_CAVEAT,\n FORMS_FILTER_CAVEAT,\n PROVISIONAL_CAVEAT,\n SEVERAL_RECORDS_CAVEAT,\n classifyEmptyPage,\n ok,\n retrievedAtSchema,\n toToolError,\n} from \"./shared.js\";\nimport type { EmptyPage, ToolResult } from \"./shared.js\";\n\nconst PERSON_TYPE = \"http://xmlns.com/foaf/0.1/Person\";\n\n/**\n * What the list holds, stated wherever it reaches a caller.\n *\n * It travels with every answer, including the ones holding no row: a page of\n * nothing is a statement about this link and about no other way the catalogue\n * credits a person, and reading it as a person having written nothing is the\n * one mistake this tool can cause.\n */\nconst SCOPE =\n \"A work is on this list because the BnF catalogue names this person as the creator of it. That is one link in the catalogue and not a bibliography: the catalogue credits a person on a record in other ways, and the BnF holds printed editions whose work it has never established as a record of its own.\";\n\n/** Wording used wherever a work's date reaches a caller. */\nconst DATE_CAVEAT =\n \"'date' and 'year' are the date of the work as the record gives it, which dates the work rather than any printing of it. A provisional record is made from a title the BnF has catalogued, so its date can be that of the printing it was made from. For the year a copy was printed, read list_editions.\";\n\n/** Wording used wherever the order of this list reaches a caller. */\nconst ORDER_CAVEAT =\n \"Rows are ordered by the address the catalogue gives each work, which is neither chronological nor an order of importance, so a first work can appear anywhere in the list. That order is the one the pages are cut along, so reading page after page reaches every work once.\";\n\nexport const listWorksDescription = [\n \"List the works the Bibliothèque nationale de France catalogue names one person the creator of, by the identifier search_authors returns. This is the tool for 'what did this person write'.\",\n \"Each row carries the title, the date the record gives the work, the form codes the catalogue points at, whether the record is established or provisional, and the identifier get_work, list_editions and find_digitised take.\",\n \"It is not a bibliography. It reports one link the catalogue holds, and the catalogue credits a person on a record in other ways and holds editions whose work it has never established, so a work missing here is not a work the person did not write.\",\n \"The form codes are the work-form vocabulary's own terms and carry no label in this dataset, so some read as words and some do not. A work stating no code has a form the catalogue does not state, so keeping the rows carrying one code finds the works that declare it and never all the works of that form.\",\n \"Rows are ordered by the address the catalogue gives each work, which is neither chronological nor an order of importance. No total is reported, because the catalogue counts nothing here and a count would read as a measure of what the person wrote.\",\n].join(\" \");\n\nexport const listWorksInput = strictInput({\n author_id: z\n .string()\n .min(1)\n .max(200)\n .describe(\n \"The person identifier from search_authors, such as 'cb119219976'. A full data.bnf.fr address is accepted too.\",\n ),\n limit: z.number().int().min(1).max(50).default(10),\n page: z.number().int().min(1).max(100).default(1),\n});\n\nexport const authoredWorkRowSchema = z.object({\n id: z.string().describe(\"Pass this to get_work, list_editions or find_digitised.\"),\n title: z.string().nullable(),\n date: z\n .string()\n .nullable()\n .describe(\n \"The date the record gives the work, as published. It dates the work rather than an edition of it.\",\n ),\n year: z.number().int().nullable().describe(\"The same date as a number, when the record has one.\"),\n forms: z\n .array(z.string())\n .describe(\n \"Terms of the BnF work-form vocabulary, as the terms themselves. The vocabulary publishes no label for them here, so they are not translated: some read as words, such as 'roman', and some do not, such as 'te'. An empty list is a form the record does not state, not a work without one.\",\n ),\n status: z\n .enum([\"established\", \"provisional\"])\n .describe(\n \"'established' is a work the BnF has settled as a record of its own. 'provisional' is a title it holds while a cataloguer settles it, whose identifier can change.\",\n ),\n source_url: z.string(),\n});\n\nexport const listWorksOutput = z.object({\n author_id: z.string(),\n works: z.array(authoredWorkRowSchema),\n page: z.number().int(),\n has_more: z.boolean(),\n retrieved_at: retrievedAtSchema,\n notes: z.array(z.string()),\n});\n\nexport type ListWorksArgs = z.infer<typeof listWorksInput>;\n\n/**\n * What a page holding no row is allowed to say.\n *\n * A page past where the rows stop and a person the catalogue links no work to\n * arrive identically, and calling either one an absence states something the\n * reading did not establish. Since no total is asked for, the only way to\n * separate them is to read the listing from its first row.\n */\nfunction emptyPageNote(emptiness: EmptyPage, page: number, authorId: string): string {\n if (emptiness === \"past_the_end\") {\n return `Page ${page} holds no row because it sits past the last row of this listing. The catalogue does link works to \"${authorId}\", and they are on earlier pages: call again with page=1, or with a lower page number, to read them.`;\n }\n if (emptiness === \"undetermined\") {\n return `Page ${page} holds no row, and the first page of the same listing could not be read to find out why. This is either the catalogue linking no work to \"${authorId}\", or a page sitting past the last row of a listing that does hold some. Call again with page=1: rows there mean the second.`;\n }\n return `The BnF catalogue links no work to \"${authorId}\" as its creator. The record exists and this link is empty on it, which is a statement about the catalogue rather than about what the person wrote. ${SEVERAL_RECORDS_CAVEAT}`;\n}\n\nfunction emptyPageBody(emptiness: EmptyPage, page: number, authorId: string): string {\n if (emptiness === \"past_the_end\") {\n return `Page ${page} of the works linked to \"${authorId}\" holds no row: it sits past the last row. Call again with page=1.`;\n }\n if (emptiness === \"undetermined\") {\n return `Page ${page} of the works linked to \"${authorId}\" holds no row, and whether the rows stop before it could not be read. Call again with page=1.`;\n }\n return `The BnF catalogue links no work to \"${authorId}\" as its creator.`;\n}\n\nexport async function runListWorks(client: BnfClient, args: ListWorksArgs): Promise<ToolResult> {\n try {\n const id = parseEntityId(args.author_id);\n const offset = (args.page - 1) * args.limit;\n const { data, cached, retrievedAt } = await client.worksByAuthor(id, args.limit, offset);\n\n const notes: string[] = [];\n if (cached) {\n notes.push(\"Served from this server's short-lived in-memory cache.\");\n }\n\n const works = data.rows.map((row) => ({\n id: row.id,\n title: row.title,\n date: row.date,\n year: row.year,\n forms: row.forms,\n status: row.status,\n source_url: row.sourceUrl,\n }));\n\n // The notes are ordered by how much a caller loses without them, because\n // the text block fits a limited trailer and drops the last of them to keep\n // the credit. What the list is comes first, then what to do next, then the\n // qualifications a caller could also read off the tool's own description.\n notes.push(SCOPE);\n\n if (data.skipped !== undefined && data.skipped > 0) {\n notes.push(\n `${data.skipped} row(s) the endpoint sent could not be read, because each named a record at an address this client cannot address. The page is short by that much rather than the catalogue holding less.`,\n );\n }\n\n let emptiness: EmptyPage = \"absent\";\n if (works.length === 0) {\n emptiness = await classifyEmptyPage(args.page, async () => {\n const first = await client.worksByAuthor(id, 1, 0);\n return first.data.rows.length > 0;\n });\n // A listing with no row anywhere says nothing until the address is known\n // to name a person. An edition, a subject heading and a record the BnF\n // describes nowhere all answer with no rows, and reporting any of them as\n // a person credited with nothing states a fact about a life the catalogue\n // never described.\n if (emptiness === \"absent\") {\n await assertPerson(client, id);\n }\n notes.push(emptyPageNote(emptiness, args.page, id.id));\n } else {\n if (data.hasMore) {\n notes.push(\n `More works exist beyond this page. Ask for page ${args.page + 1}. This server reports no total: a count of the works one link reaches would read as a count of what the person wrote, and the catalogue does not carry that.`,\n );\n }\n notes.push(`${FORMS_CAVEAT} ${FORMS_FILTER_CAVEAT}`);\n if (works.some((work) => work.status === \"provisional\")) {\n notes.push(PROVISIONAL_CAVEAT);\n }\n notes.push(ORDER_CAVEAT, DATE_CAVEAT);\n }\n\n const body =\n works.length === 0\n ? emptyPageBody(emptiness, args.page, id.id)\n : `${works.length} work(s) the catalogue links to \"${id.id}\" as their creator:\\n${works\n .map((work, index) => {\n const when = work.date ? ` (${work.date})` : \"\";\n const forms = work.forms.length > 0 ? ` · form: ${work.forms.join(\", \")}` : \"\";\n const status = work.status === \"provisional\" ? \" · provisional record\" : \"\";\n return `${index + 1}. ${work.title ?? work.id}${when}${forms}${status} · id: ${work.id}\\n ${work.source_url}`;\n })\n .join(\"\\n\")}`;\n\n return ok(\n {\n author_id: id.id,\n works,\n page: args.page,\n has_more: data.hasMore,\n retrieved_at: retrievedAt,\n notes,\n },\n body,\n { retrievedAt, notes, sourceUrl: id.pageUrl },\n );\n } catch (error) {\n return toToolError(error);\n }\n}\n\n/** Refuse an address the catalogue describes as anything other than a person. */\nasync function assertPerson(client: BnfClient, id: EntityId): Promise<void> {\n const types = await client.types(id);\n if (types.data.length === 0) {\n throw notFound(`data.bnf.fr describes nothing at \"${id.id}\".`, {\n hint: \"Find the identifier with search_authors rather than writing one.\",\n url: id.pageUrl,\n });\n }\n if (!types.data.includes(PERSON_TYPE)) {\n throw notFound(\n `\"${id.id}\" is described by data.bnf.fr, and it is not a person: it is typed ${types.data.join(\", \")}.`,\n {\n hint: \"This tool walks from a person to the works they are credited with. For a work, list_editions reads its editions and get_work reads the record.\",\n url: id.pageUrl,\n },\n );\n }\n}\n","/**\n * search_authors: find a person in the BnF authority file by name.\n *\n * One person can hold several authority records. \"Arthur Rimbaud\" answers with\n * two, `cb119219976` and `cb11921998j`, and neither is a mistake: the file\n * distinguishes the poet from a second heading the BnF keeps for him. Returning\n * one of the two and calling it the answer would hide a choice the caller is\n * the only one able to make, so every match is returned and the answer says\n * when several rows carry one name.\n */\n\nimport { z } from \"zod\";\nimport type { BnfClient } from \"../bnf/client.js\";\nimport { TEXT_WINDOW } from \"../bnf/queries.js\";\nimport { invalidInput } from \"../errors.js\";\nimport { readSearchText } from \"../bnf/sparql.js\";\nimport { strictInput } from \"./arguments.js\";\nimport {\n NO_RANKING,\n WINDOW_FULL_AND_NO_MORE,\n WINDOW_FULL_CAVEAT,\n classifyEmptyPage,\n headingYearConflicts,\n ok,\n readingNotes,\n retrievedAtSchema,\n toToolError,\n} from \"./shared.js\";\nimport type { EmptyPage, ToolResult } from \"./shared.js\";\n\n/** The joiners a name carries, which an index may or may not keep. */\nconst JOINER = /['-]/u;\n\nexport const searchAuthorsDescription = [\n \"Find a person in the Bibliothèque nationale de France authority file by name, and get the identifier the other tools take.\",\n \"This matches the name the BnF records, so it takes a surname, a full name or both names in either order. It does not read biographies, so it cannot find a person from what they wrote or what they did.\",\n \"It reads the person records and nothing else: an organisation, a conference or a place is outside it, and an answer holding no row says nothing about those.\",\n \"The match is letter for letter, so a name written with other accents or under another transliteration is a different search: try the spellings a library would use before concluding the BnF holds nobody of that name.\",\n \"Several rows can carry one name: the BnF keeps more than one authority record for some people, and many people share a name. Read 'birth_year', 'death_year' and 'role' to tell them apart, and show the caller the choice rather than picking one.\",\n \"Rows come back ordered by the address of the record, which is not an order of relevance. That order is the one the pages are cut along, so paging through a search reaches every match once.\",\n \"One search reads a fixed window of the index. 'index_window_full' says whether that window came back full: when it did, names sit past what was read, 'has_more' being false says where the reading stopped, and another reading of the same search can bring back other rows. Narrow the name to reach further.\",\n].join(\" \");\n\nexport const searchAuthorsInput = strictInput({\n name: z\n .string()\n .min(1)\n .max(200)\n .describe(\"The person's name, such as 'Rimbaud' or 'Arthur Rimbaud'.\"),\n limit: z.number().int().min(1).max(50).default(10),\n page: z.number().int().min(1).max(100).default(1),\n});\n\nexport const authorRowSchema = z.object({\n id: z.string().describe(\"Pass this to get_author, or to find_digitised.\"),\n name: z.string().nullable().describe(\"The name as data.bnf.fr writes it.\"),\n label: z\n .string()\n .nullable()\n .describe(\"The authority heading, which usually carries the dates in brackets.\"),\n birth_year: z.number().int().nullable(),\n death_year: z.number().int().nullable().describe(\"Null when the record states none.\"),\n role: z\n .string()\n .nullable()\n .describe(\n \"What the record says the person did. This field holds a job title far more often than a sentence, and is frequently a single word.\",\n ),\n source_url: z.string().describe(\"The record's page. Show this when citing it.\"),\n});\n\nexport const searchAuthorsOutput = z.object({\n name: z.string().describe(\"The name asked for.\"),\n words_searched: z\n .array(z.string())\n .describe(\n \"The terms the index required, each of which has to appear in the name. A word is cut at an apostrophe and at a hyphen before the index sees it, so a name written as one word can appear here as two, and a record carrying the pieces apart is a match.\",\n ),\n authors: z.array(authorRowSchema),\n page: z.number().int(),\n has_more: z\n .boolean()\n .describe(\n \"Whether the endpoint held at least one further row beyond this page, within the window the search read off the index. Read it alongside 'index_window_full': false on a full window says where the reading stopped rather than that the file holds nobody else.\",\n ),\n index_window_full: z\n .boolean()\n .nullable()\n .describe(\n \"Whether the fixed window this search reads off the index came back full. True means records carrying these words sit past what was read, so the rows here are a part of the matches and another reading can bring back other rows. False means the window held everything that matched. Null when the endpoint stated no occupancy, which claims nothing either way.\",\n ),\n retrieved_at: retrievedAtSchema,\n notes: z.array(z.string()),\n});\n\nexport type SearchAuthorsArgs = z.infer<typeof searchAuthorsInput>;\n\n/** Wording used wherever the spelling of a name decides what a search reaches. */\nconst SPELLING_CAVEAT =\n \"The index matches the words letter for letter, so a name written with other accents, or under another transliteration, is a different search reaching different records. A short answer is no evidence that the file holds nobody else of that name: ask again with the accents dropped, with them restored, and under the transliterations a library uses.\";\n\n/**\n * The years a record states, written so that a silence stays a silence.\n *\n * Printing \"1940–?\" hands a reader a glyph to interpret, and most read it as a\n * death on a date nobody wrote down. A record carrying a birth year and no death\n * year says one thing: when the person was born.\n */\nfunction lifespan(birthYear: number | null, deathYear: number | null): string {\n if (birthYear !== null && deathYear !== null) {\n return ` (${birthYear}–${deathYear})`;\n }\n if (birthYear !== null) {\n return ` (born ${birthYear})`;\n }\n if (deathYear !== null) {\n return ` (died ${deathYear})`;\n }\n return \"\";\n}\n\n/**\n * What a page of no rows is allowed to say.\n *\n * Only a search whose first row is missing too is an absence. A page past where\n * the rows stop is a fact about the page asked for, and it is written as such:\n * the page, that it holds nothing, and the way back to the rows. No count of\n * what came before appears, because this server never asks the endpoint how\n * many records match, so where the rows stop is a boundary it can point at and\n * not a number it can state.\n */\nfunction emptyPageNote(\n emptiness: EmptyPage,\n page: number,\n words: string[],\n windowFull: boolean,\n): string {\n const quoted = words.map((word) => `\"${word}\"`).join(\", \");\n // A window filled by records of another kind leaves the filter nothing to\n // keep, and the page that comes back is the same empty page an unmatched name\n // produces. Only one of the two is an absence, and the window says which.\n if (windowFull) {\n return `The window this search reads off the index came back full, and no person record survived the reading: every row of it named something the authority file types as another kind of heading. Whether a person carries ${quoted} in a name is unanswered here rather than answered no. Narrow the search, by adding a word or by writing a fuller form of the name, so that the window reaches further into the index.`;\n }\n if (emptiness === \"past_the_end\") {\n return `Page ${page} holds no row because it sits past the last row of this search. Records do carry ${quoted} in a name, and they are on earlier pages: call again with page=1, or with a lower page number, to read them.`;\n }\n if (emptiness === \"undetermined\") {\n return `Page ${page} holds no row, and reading the first page of the same search to find out why did not answer. This is either a name carrying ${quoted} matching no record, or a page sitting past the last row of a search that does match. Call again with page=1: rows there mean the second.`;\n }\n return `No person record in the BnF authority file carries every one of these words in a name: ${quoted}. This search reads the person records and nothing else, so an organisation, a conference, a place or a subject heading is outside what was looked at, whether or not the BnF holds a heading for one. It reads names, so a person known by a pen name is found under that name rather than their own.`;\n}\n\nfunction emptyPageBody(\n emptiness: EmptyPage,\n page: number,\n name: string,\n windowFull: boolean,\n): string {\n if (windowFull) {\n return `No person record survived the reading for \"${name}\", out of a window of the index that came back full. Narrow the search.`;\n }\n if (emptiness === \"past_the_end\") {\n return `Page ${page} of the search for \"${name}\" holds no row: it sits past the last row. Call again with page=1.`;\n }\n if (emptiness === \"undetermined\") {\n return `Page ${page} of the search for \"${name}\" holds no row, and whether the rows stop before it could not be read. Call again with page=1.`;\n }\n return `No person in the BnF authority file matches \"${name}\".`;\n}\n\nexport async function runSearchAuthors(\n client: BnfClient,\n args: SearchAuthorsArgs,\n): Promise<ToolResult> {\n try {\n const reading = readSearchText(args.name);\n const words = reading.words;\n if (words.length === 0) {\n return toToolError(\n invalidInput(\n `\"${args.name}\" holds no word to search for.`,\n \"Write the name in letters, such as 'Rimbaud'.\",\n ),\n );\n }\n\n // A text search reads a fixed window of the index, and a page beyond it\n // would come back empty for want of anything having been read. Answering\n // that with \"the BnF holds none\" states an absence nobody established, so\n // the page is refused here rather than bought from the endpoint.\n const offset = (args.page - 1) * args.limit;\n if (offset + args.limit > TEXT_WINDOW) {\n return toToolError(\n invalidInput(\n `Page ${args.page} at ${args.limit} rows a page reaches past row ${TEXT_WINDOW}, which is as far into the index as one search reads.`,\n `Narrow the search instead. Rows past ${TEXT_WINDOW} are not a smaller part of the answer: they were never read, so an empty page there would say nothing about the catalogue.`,\n ),\n );\n }\n const { data, cached, retrievedAt } = await client.searchAuthors(args.name, args.limit, offset);\n\n const terms = reading.terms;\n const windowFull = data.indexWindowFull ?? null;\n\n const notes: string[] = [];\n if (cached) {\n notes.push(\"Served from this server's short-lived in-memory cache.\");\n }\n\n // What bounds the answer is said before what qualifies it. The text block\n // fits a limited trailer and drops the last notes to keep the credit, and a\n // caller losing this one is left reading a part of the matches as all of\n // them.\n if (windowFull) {\n notes.push(WINDOW_FULL_CAVEAT);\n if (!data.hasMore) {\n notes.push(WINDOW_FULL_AND_NO_MORE);\n }\n }\n\n // A caller checking why a row is on the list reads the terms, so the ones\n // the index made out of a word are traced back to it before anything that\n // merely qualifies the answer.\n const split = words.filter((word) => JOINER.test(word));\n if (split.length > 0) {\n notes.push(\n `${split.map((word) => `\"${word}\"`).join(\", \")} reached the index as separate terms: it cuts a word at an apostrophe and at a hyphen, and requires each piece to appear in the name rather than in that arrangement, so a record writing the pieces apart is a match.`,\n );\n }\n notes.push(...readingNotes(reading, \"name\"));\n\n const authors = data.rows.map((row) => ({\n id: row.id,\n name: row.name,\n label: row.label,\n birth_year: row.birthYear,\n death_year: row.deathYear,\n role: row.role,\n source_url: row.sourceUrl,\n }));\n\n if (authors.length > 0) {\n notes.push(NO_RANKING);\n }\n\n // The index compares the characters it was given against the characters a\n // cataloguer entered. A name the BnF holds under a transliteration of its\n // own is therefore reachable only by that spelling, and the answer to any\n // other one is a short list or none at all, neither of which looks partial.\n notes.push(SPELLING_CAVEAT);\n\n const disputed = authors\n .map((author) => ({\n label: author.label ?? author.name,\n conflicts: headingYearConflicts(author.label, author.birth_year, author.death_year),\n }))\n .filter((row) => row.conflicts.length > 0);\n if (disputed.length > 0) {\n notes.push(\n `${disputed.map((row) => `\"${row.label}\" states ${row.conflicts.join(\", and \")}`).join(\"; \")}. A record stating its dates twice can disagree with itself, and the BnF publishes both. These fields are what tells two people of one name apart, so open source_url before telling them apart by a date the record disputes.`,\n );\n }\n\n // Several records under one name is the case this tool exists to surface.\n const repeated = new Map<string, number>();\n for (const author of authors) {\n const key = (author.name ?? \"\").toLocaleLowerCase(\"fr\");\n if (key !== \"\") {\n repeated.set(key, (repeated.get(key) ?? 0) + 1);\n }\n }\n const shared = [...repeated.entries()].filter(([, count]) => count > 1);\n if (shared.length > 0) {\n notes.push(\n `${shared.map(([name, count]) => `${count} records are named \"${name}\"`).join(\"; \")}. The BnF keeps more than one authority record for some people, and different people share names. Read the dates and the role before treating any of them as the person meant.`,\n );\n }\n\n if (data.hasMore) {\n notes.push(\n `More matches exist beyond this page. Ask for page ${args.page + 1}, or narrow the name. This server reports no total, because counting every match means asking the endpoint to walk the whole index a second time.`,\n );\n }\n // A page holding no row is read for what it is before anything is said\n // about the authority file: no name matching, and the rows of a name that\n // does match stopping earlier, look identical from the page alone.\n let emptiness: EmptyPage = \"absent\";\n if (authors.length === 0) {\n // A full window already accounts for the page, and reading the search\n // from its first row would answer a question the window has settled.\n emptiness = windowFull\n ? \"absent\"\n : await classifyEmptyPage(args.page, async () => {\n const first = await client.searchAuthors(args.name, 1, 0);\n return first.data.rows.length > 0;\n });\n notes.push(emptyPageNote(emptiness, args.page, terms, windowFull === true));\n }\n\n const body =\n authors.length === 0\n ? emptyPageBody(emptiness, args.page, args.name, windowFull === true)\n : `${authors.length} record(s) for \"${args.name}\":\\n${authors\n .map((author, index) => {\n const life = lifespan(author.birth_year, author.death_year);\n const role = author.role ? ` · ${author.role}` : \"\";\n return `${index + 1}. ${author.name ?? author.label ?? author.id}${life}${role} · id: ${author.id}\\n ${author.source_url}`;\n })\n .join(\"\\n\")}`;\n\n return ok(\n {\n name: args.name,\n words_searched: terms,\n authors,\n page: args.page,\n has_more: data.hasMore,\n index_window_full: windowFull,\n retrieved_at: retrievedAt,\n notes,\n },\n body,\n { retrievedAt, notes },\n );\n } catch (error) {\n return toToolError(error);\n }\n}\n","/**\n * search_works: find a work by words in its title.\n *\n * The endpoint's full-text index answers one question: does this title carry\n * every one of these words. It returns no score, and the rows come back in the\n * order of the record's address, which measures nothing about the match.\n * Searching for \"saison enfer\" therefore returns a dozen studies of Rimbaud\n * before Rimbaud, and every one of them is a correct match.\n *\n * So this tool never presents its rows as a ranking, and never presents the\n * first row as the answer. Saying that plainly is worth more than any\n * re-ordering this server could invent, since an order this server made up\n * would be a claim the catalogue does not support.\n */\n\nimport { z } from \"zod\";\nimport type { BnfClient } from \"../bnf/client.js\";\nimport { TEXT_WINDOW } from \"../bnf/queries.js\";\nimport { invalidInput } from \"../errors.js\";\nimport { readSearchText } from \"../bnf/sparql.js\";\nimport { strictInput } from \"./arguments.js\";\nimport {\n NO_RANKING,\n PROVISIONAL_CAVEAT,\n WINDOW_FULL_AND_NO_MORE,\n WINDOW_FULL_CAVEAT,\n classifyEmptyPage,\n ok,\n readingNotes,\n retrievedAtSchema,\n toToolError,\n} from \"./shared.js\";\nimport type { EmptyPage, ToolResult } from \"./shared.js\";\n\n/** The joiners a name carries, which an index may or may not keep. */\nconst JOINER = /['-]/u;\n\nexport const searchWorksDescription = [\n \"Find a work in the Bibliothèque nationale de France catalogue by words in its title, and get the identifier get_work and list_editions take.\",\n \"A row matches when its title carries every word given. The index returns no measure of how well a row matches, so the rows are ordered by the address of the record and the work a person would call the obvious answer can sit anywhere in the list or on a later page.\",\n \"A study of a book carries the book's title, so a search for a famous title returns the criticism alongside the work. Read 'creators' to tell them apart.\",\n \"This reads titles and nothing else. Searching a person's name returns the works written about them, whose creators are their critics; list_works is what walks from a person to the works the catalogue names them the creator of.\",\n \"'status' says whether the BnF has established the work as a record of its own or holds it provisionally under a title it has catalogued.\",\n \"One search reads a fixed window of the index, and that window is read before the works are told apart from everything else the index holds, so even a short list can rest on a full one. 'index_window_full' says which: when it is true, titles sit past what was read, 'has_more' being false says where the reading stopped, and another reading of the same search can bring back other rows. Add a word to the title to reach further.\",\n].join(\" \");\n\nexport const searchWorksInput = strictInput({\n title: z\n .string()\n .min(1)\n .max(200)\n .describe(\"Words from the title, such as 'saison enfer'. Every word given has to appear.\"),\n limit: z.number().int().min(1).max(50).default(10),\n page: z.number().int().min(1).max(100).default(1),\n});\n\nexport const workRowSchema = z.object({\n id: z.string().describe(\"Pass this to get_work, list_editions or find_digitised.\"),\n title: z.string().nullable(),\n date: z.string().nullable().describe(\"The year the record gives the work, as published.\"),\n creators: z\n .array(z.object({ id: z.string(), name: z.string().nullable() }))\n .describe(\"Everyone the record credits. Empty when the record credits nobody.\"),\n status: z\n .enum([\"established\", \"provisional\"])\n .describe(\n \"'established' is a work the BnF has settled as a record of its own. 'provisional' is a title it holds while a cataloguer settles it, whose identifier can change.\",\n ),\n source_url: z.string(),\n});\n\nexport const searchWorksOutput = z.object({\n title: z.string().describe(\"The words asked for.\"),\n words_searched: z\n .array(z.string())\n .describe(\n \"The terms the index required, each of which has to appear in the title. A word is cut at an apostrophe and at a hyphen before the index sees it, so a word given as one can appear here as two, and a record carrying the pieces apart is a match.\",\n ),\n works: z.array(workRowSchema),\n page: z.number().int(),\n has_more: z\n .boolean()\n .describe(\n \"Whether the endpoint held at least one further row beyond this page, within the window the search read off the index. Read it alongside 'index_window_full': false on a full window says where the reading stopped rather than that the catalogue holds nothing else.\",\n ),\n index_window_full: z\n .boolean()\n .nullable()\n .describe(\n \"Whether the fixed window this search reads off the index came back full. The window is read before the works are separated from the rest of the index, so a short list can rest on a full window. True means titles carrying these words sit past what was read, and another reading can bring back other rows. False means the window held everything that matched. Null when the endpoint stated no occupancy.\",\n ),\n retrieved_at: retrievedAtSchema,\n notes: z.array(z.string()),\n});\n\nexport type SearchWorksArgs = z.infer<typeof searchWorksInput>;\n\n/**\n * What a page of no rows is allowed to say.\n *\n * Only a search whose first row is missing too is an absence. A page past where\n * the rows stop is a fact about the page asked for, and it is written as such:\n * the page, that it holds nothing, and the way back to the rows. No count of\n * what came before appears, because this server never asks the endpoint how\n * many records match, so where the rows stop is a boundary it can point at and\n * not a number it can state.\n */\nfunction emptyPageNote(\n emptiness: EmptyPage,\n page: number,\n words: string[],\n windowFull: boolean,\n): string {\n const quoted = words.map((word) => `\"${word}\"`).join(\", \");\n // The window is read before works are told apart from the rest of the index,\n // so a full one can leave nothing for the answer while the catalogue holds\n // matching works further along. That page and a page of no matches at all are\n // the same page, and the window is what separates them.\n if (windowFull) {\n return `The window this search reads off the index came back full, and no work record survived the reading: every row of it named something the catalogue types as another kind of record. Whether a work carries ${quoted} in its title is unanswered here rather than answered no. Add a word to the title so that the window reaches further into the index.`;\n }\n if (emptiness === \"past_the_end\") {\n return `Page ${page} holds no row because it sits past the last row of this search. Works do carry ${quoted} in their title, and they are on earlier pages: call again with page=1, or with a lower page number, to read them.`;\n }\n if (emptiness === \"undetermined\") {\n return `Page ${page} holds no row, and reading the first page of the same search to find out why did not answer. This is either a title carrying ${quoted} matching no record, or a page sitting past the last row of a search that does match. Call again with page=1: rows there mean the second.`;\n }\n return `No work in the BnF catalogue has a title carrying every one of these words: ${quoted}. Every word given has to appear, so dropping one widens the search.`;\n}\n\nfunction emptyPageBody(\n emptiness: EmptyPage,\n page: number,\n title: string,\n windowFull: boolean,\n): string {\n if (windowFull) {\n return `No work record survived the reading for \"${title}\", out of a window of the index that came back full. Add a word to the title.`;\n }\n if (emptiness === \"past_the_end\") {\n return `Page ${page} of the search for \"${title}\" holds no row: it sits past the last row. Call again with page=1.`;\n }\n if (emptiness === \"undetermined\") {\n return `Page ${page} of the search for \"${title}\" holds no row, and whether the rows stop before it could not be read. Call again with page=1.`;\n }\n return `No work in the BnF catalogue matches \"${title}\".`;\n}\n\nexport async function runSearchWorks(\n client: BnfClient,\n args: SearchWorksArgs,\n): Promise<ToolResult> {\n try {\n const reading = readSearchText(args.title);\n const words = reading.words;\n if (words.length === 0) {\n return toToolError(\n invalidInput(\n `\"${args.title}\" holds no word to search for.`,\n \"Write the words of the title in letters, such as 'saison enfer'.\",\n ),\n );\n }\n\n // A text search reads a fixed window of the index, and a page beyond it\n // would come back empty for want of anything having been read. Answering\n // that with \"the BnF holds none\" states an absence nobody established, so\n // the page is refused here rather than bought from the endpoint.\n const offset = (args.page - 1) * args.limit;\n if (offset + args.limit > TEXT_WINDOW) {\n return toToolError(\n invalidInput(\n `Page ${args.page} at ${args.limit} rows a page reaches past row ${TEXT_WINDOW}, which is as far into the index as one search reads.`,\n `Narrow the search instead. Rows past ${TEXT_WINDOW} are not a smaller part of the answer: they were never read, so an empty page there would say nothing about the catalogue.`,\n ),\n );\n }\n const { data, cached, retrievedAt } = await client.searchWorks(args.title, args.limit, offset);\n\n const terms = reading.terms;\n const windowFull = data.indexWindowFull ?? null;\n\n const notes: string[] = [];\n if (cached) {\n notes.push(\"Served from this server's short-lived in-memory cache.\");\n }\n\n // What bounds the answer is said before what qualifies it. The text block\n // fits a limited trailer and drops the last notes to keep the credit, and a\n // caller losing this one is left reading a part of the matches as all of\n // them.\n if (windowFull) {\n notes.push(WINDOW_FULL_CAVEAT);\n if (!data.hasMore) {\n notes.push(WINDOW_FULL_AND_NO_MORE);\n }\n }\n\n // A caller checking why a row is on the list reads the terms, so the ones\n // the index made out of a word are traced back to it before anything that\n // merely qualifies the answer.\n const split = words.filter((word) => JOINER.test(word));\n if (split.length > 0) {\n notes.push(\n `${split.map((word) => `\"${word}\"`).join(\", \")} reached the index as separate terms: it cuts a word at an apostrophe and at a hyphen, and requires each piece to appear in the title rather than in that arrangement, so a record writing the pieces apart is a match.`,\n );\n }\n notes.push(...readingNotes(reading, \"title\"));\n\n const works = data.rows.map((row) => ({\n id: row.id,\n title: row.title,\n date: row.date,\n creators: row.creators,\n status: row.status,\n source_url: row.sourceUrl,\n }));\n\n if (works.length > 0) {\n notes.push(NO_RANKING);\n }\n if (works.some((work) => work.status === \"provisional\")) {\n notes.push(PROVISIONAL_CAVEAT);\n }\n\n // Writing a title out in full narrows the search rather than sharpening it.\n // \"Une saison en enfer\" requires \"une\" and \"en\" to appear as well, so a\n // record catalogued as \"Saison en enfer\" falls out of the answer, and the\n // caller sees a shorter list and reads it as the BnF holding less. Which\n // words carry the meaning is not something this server can tell: \"mal\" and\n // \"les\" are the same length, so the requirement is stated and the choice of\n // what to drop is left where it belongs.\n if (terms.length > 2) {\n notes.push(\n `All ${terms.length} terms were required: ${terms.map((term) => `\"${term}\"`).join(\", \")}. A record catalogued under a shorter form of the title carries fewer of them and falls out of the answer, so dropping a word widens the search.`,\n );\n }\n\n // A page of studies about a famous work, with the work itself further down\n // the index, otherwise reads as the BnF not holding the work at all.\n if (works.length > 0 && works.every((work) => work.status === \"provisional\")) {\n notes.push(\n \"Every row on this page is a provisional record. The BnF creates one for a title it has catalogued without settling it as a work of its own, which is what a study of a famous book usually gets. An established record for the work itself can sit further down the index: ask for the next page, or search fewer words of the title.\",\n );\n }\n\n if (data.hasMore) {\n notes.push(\n `More matches exist beyond this page. Ask for page ${args.page + 1}, or add a word to the title. This server reports no total: a count would read as a measure of the search, and this search has none.`,\n );\n }\n // A page holding no row is read for what it is before anything is said\n // about the catalogue: the words matching nothing, and the rows of a search\n // that does match stopping earlier, look identical from the page alone.\n let emptiness: EmptyPage = \"absent\";\n if (works.length === 0) {\n // A full window already accounts for the page, and reading the search\n // from its first row would answer a question the window has settled.\n emptiness = windowFull\n ? \"absent\"\n : await classifyEmptyPage(args.page, async () => {\n const first = await client.searchWorks(args.title, 1, 0);\n return first.data.rows.length > 0;\n });\n notes.push(emptyPageNote(emptiness, args.page, terms, windowFull === true));\n }\n\n const body =\n works.length === 0\n ? emptyPageBody(emptiness, args.page, args.title, windowFull === true)\n : `${works.length} work(s) whose title carries ${terms.map((term) => `\"${term}\"`).join(\" and \")}:\\n${works\n .map((work, index) => {\n const by =\n work.creators.length > 0\n ? ` · ${work.creators.map((creator) => creator.name ?? creator.id).join(\", \")}`\n : \"\";\n const when = work.date ? ` (${work.date})` : \"\";\n const status = work.status === \"provisional\" ? \" · provisional record\" : \"\";\n return `${index + 1}. ${work.title ?? work.id}${when}${by}${status} · id: ${work.id}\\n ${work.source_url}`;\n })\n .join(\"\\n\")}`;\n\n return ok(\n {\n title: args.title,\n words_searched: terms,\n works,\n page: args.page,\n has_more: data.hasMore,\n index_window_full: windowFull,\n retrieved_at: retrievedAt,\n notes,\n },\n body,\n { retrievedAt, notes },\n );\n } catch (error) {\n return toToolError(error);\n }\n}\n"],"mappings":"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","names":["process","text","page","z","z","z","text","page","z","z","z","z","notesOnWhatTheRecordStates","z","z","page","z","z","PERSON_TYPE","z","page","z","z","emptyPageNote","page","emptyPageBody","z","JOINER","z","emptyPageNote","page","emptyPageBody","process"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/server.ts","../src/config.ts","../src/version.ts","../src/errors.ts","../src/bnf/cache.ts","../src/bnf/endpoint.ts","../src/bnf/http.ts","../src/bnf/sparql.ts","../src/bnf/queries.ts","../src/bnf/parse.ts","../src/bnf/rateLimiter.ts","../src/bnf/client.ts","../src/tools/findDigitised.ts","../src/tools/arguments.ts","../src/tools/shared.ts","../src/tools/getAuthor.ts","../src/tools/getWork.ts","../src/tools/listEditions.ts","../src/tools/listWorks.ts","../src/tools/searchAuthors.ts","../src/tools/searchWorks.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n * Entry point.\n *\n * stdout carries the protocol and nothing else: every diagnostic goes to\n * stderr, because a stray line on stdout corrupts the session for the client.\n */\n\nimport process from \"node:process\";\nimport { StdioServerTransport } from \"@modelcontextprotocol/sdk/server/stdio.js\";\nimport { createServer } from \"./server.js\";\n\nasync function main(): Promise<void> {\n const server = createServer();\n const transport = new StdioServerTransport();\n await server.connect(transport);\n\n const shutdown = async () => {\n await server.close().catch(() => undefined);\n process.exit(0);\n };\n process.on(\"SIGINT\", shutdown);\n process.on(\"SIGTERM\", shutdown);\n}\n\nmain().catch((error: unknown) => {\n const message = error instanceof Error ? error.message : String(error);\n process.stderr.write(`[mcp-databnf] fatal: ${message}\\n`);\n process.exit(1);\n});\n","/**\n * Wiring: one client, seven tools, and the guidance a model reads before using\n * any of them.\n */\n\nimport { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { BnfClient } from \"./bnf/client.js\";\nimport type { Config, Logger } from \"./config.js\";\nimport { createLogger, loadConfig } from \"./config.js\";\nimport {\n findDigitisedDescription,\n findDigitisedInput,\n findDigitisedOutput,\n runFindDigitised,\n} from \"./tools/findDigitised.js\";\nimport type { FindDigitisedArgs } from \"./tools/findDigitised.js\";\nimport {\n getAuthorDescription,\n getAuthorInput,\n getAuthorOutput,\n runGetAuthor,\n} from \"./tools/getAuthor.js\";\nimport type { GetAuthorArgs } from \"./tools/getAuthor.js\";\nimport { getWorkDescription, getWorkInput, getWorkOutput, runGetWork } from \"./tools/getWork.js\";\nimport type { GetWorkArgs } from \"./tools/getWork.js\";\nimport {\n listEditionsDescription,\n listEditionsInput,\n listEditionsOutput,\n runListEditions,\n} from \"./tools/listEditions.js\";\nimport type { ListEditionsArgs } from \"./tools/listEditions.js\";\nimport {\n listWorksDescription,\n listWorksInput,\n listWorksOutput,\n runListWorks,\n} from \"./tools/listWorks.js\";\nimport type { ListWorksArgs } from \"./tools/listWorks.js\";\nimport {\n runSearchAuthors,\n searchAuthorsDescription,\n searchAuthorsInput,\n searchAuthorsOutput,\n} from \"./tools/searchAuthors.js\";\nimport type { SearchAuthorsArgs } from \"./tools/searchAuthors.js\";\nimport {\n runSearchWorks,\n searchWorksDescription,\n searchWorksInput,\n searchWorksOutput,\n} from \"./tools/searchWorks.js\";\nimport type { SearchWorksArgs } from \"./tools/searchWorks.js\";\nimport { PKG_VERSION } from \"./version.js\";\n\nexport interface CreateServerOptions {\n config?: Config;\n logger?: Logger;\n fetchImpl?: typeof fetch;\n}\n\n/** Nothing here writes or deletes; every tool only reads. */\nconst READ_ONLY = {\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: true,\n} as const;\n\nexport const INSTRUCTIONS = [\n \"Tools for data.bnf.fr, the open catalogue of the Bibliothèque nationale de France. No API key and no account are needed.\",\n \"The usual path is search_authors or search_works to get an identifier, then get_author, get_work or list_editions to read the record it names. What a person wrote is list_works, which walks from a person to the works the catalogue names them the creator of; a title search cannot answer that, because it reads titles and a person's name in a title belongs to the books written about them.\",\n \"A list of a person's works is one link the catalogue holds and not a bibliography: the catalogue credits a person on a record in other ways, and the BnF holds editions whose work it has never established as a record of its own, so a work missing from the list is not a work the person did not write. The catalogue states a work's form as a code from a vocabulary it publishes no label for, and it states no form at all on many works, so keeping the rows carrying one code finds the works that declare it and never all the works of that form.\",\n \"The BnF publishes these metadata on one condition: name the source and state the date they were retrieved. Every answer carries 'retrieved_at' for that reason, and repeating both alongside what you show is what the licence asks for.\",\n \"Both searches read a full-text index that answers whether a title or a name matches and never scores how well, so rows come back ordered by the address of the record. Never present the first row as the best answer, and never describe a count as a ranking. Every listing here is paged along that same order, so a page and the page after it hold different records and nothing between them is skipped.\",\n \"One person can hold several authority records, and search_authors returns all of them: show the caller the choice rather than picking one.\",\n \"A work record is either established, with an ARK of its own, or provisional, addressed under 'temp-work' while a cataloguer settles it. A provisional identifier can change, so prefer an established one when citing.\",\n \"The catalogue describes what the BnF holds; it does not hold the texts. A record can point at a digitised copy on Gallica, and those links are returned as addresses for a person to open. This server never requests gallica.bnf.fr, because the BnF places its metadata and its digitised contents under two different regimes, so it can say a document exists at a link and nothing about what is there.\",\n \"This server paces itself, and a rate_limited error means the endpoint was asked to slow down, never that the thing you asked for is missing.\",\n \"Every result carries a source_url. Credit data.bnf.fr and link what you use.\",\n].join(\" \");\n\nexport function createServer(options: CreateServerOptions = {}): McpServer {\n const config = options.config ?? loadConfig();\n const logger = options.logger ?? createLogger(config.logLevel);\n const client = new BnfClient({\n config,\n logger,\n ...(options.fetchImpl ? { fetchImpl: options.fetchImpl } : {}),\n });\n\n const server = new McpServer(\n { name: \"mcp-databnf\", version: PKG_VERSION },\n { instructions: INSTRUCTIONS },\n );\n\n server.registerTool(\n \"search_authors\",\n {\n title: \"Find a person in the BnF authority file\",\n description: searchAuthorsDescription,\n inputSchema: searchAuthorsInput,\n outputSchema: searchAuthorsOutput,\n annotations: READ_ONLY,\n },\n async (args) => runSearchAuthors(client, args as SearchAuthorsArgs),\n );\n\n server.registerTool(\n \"get_author\",\n {\n title: \"Read a person's record\",\n description: getAuthorDescription,\n inputSchema: getAuthorInput,\n outputSchema: getAuthorOutput,\n annotations: READ_ONLY,\n },\n async (args) => runGetAuthor(client, args as GetAuthorArgs),\n );\n\n server.registerTool(\n \"search_works\",\n {\n title: \"Find a work by its title\",\n description: searchWorksDescription,\n inputSchema: searchWorksInput,\n outputSchema: searchWorksOutput,\n annotations: READ_ONLY,\n },\n async (args) => runSearchWorks(client, args as SearchWorksArgs),\n );\n\n server.registerTool(\n \"get_work\",\n {\n title: \"Read a work's record\",\n description: getWorkDescription,\n inputSchema: getWorkInput,\n outputSchema: getWorkOutput,\n annotations: READ_ONLY,\n },\n async (args) => runGetWork(client, args as GetWorkArgs),\n );\n\n server.registerTool(\n \"list_editions\",\n {\n title: \"List the editions of a work\",\n description: listEditionsDescription,\n inputSchema: listEditionsInput,\n outputSchema: listEditionsOutput,\n annotations: READ_ONLY,\n },\n async (args) => runListEditions(client, args as ListEditionsArgs),\n );\n\n server.registerTool(\n \"list_works\",\n {\n title: \"List the works a person is credited with\",\n description: listWorksDescription,\n inputSchema: listWorksInput,\n outputSchema: listWorksOutput,\n annotations: READ_ONLY,\n },\n async (args) => runListWorks(client, args as ListWorksArgs),\n );\n\n server.registerTool(\n \"find_digitised\",\n {\n title: \"Gather the digitised documents attached to a record\",\n description: findDigitisedDescription,\n inputSchema: findDigitisedInput,\n outputSchema: findDigitisedOutput,\n annotations: READ_ONLY,\n },\n async (args) => runFindDigitised(client, args as FindDigitisedArgs),\n );\n\n logger.info(\n `ready: user-agent=\"${config.userAgent}\", ${config.minIntervalMs}ms between requests, cache ${config.cacheTtlMs}ms`,\n );\n\n return server;\n}\n","/**\n * Settings, read from the environment.\n *\n * A value that cannot be read warns and falls back rather than stopping the\n * server: a typo in one variable should not take away every tool. Warnings go\n * to stderr, because stdout carries the protocol and anything written there\n * corrupts the session.\n */\n\nimport process from \"node:process\";\nimport { PKG_VERSION, REPO_URL } from \"./version.js\";\n\nexport const LOG_LEVELS = [\"silent\", \"error\", \"info\", \"debug\"] as const;\nexport type LogLevel = (typeof LOG_LEVELS)[number];\n\n/**\n * The narrowest spacing this client will ever use, in milliseconds.\n *\n * The BnF publishes no rate for the SPARQL endpoint. It publishes\n * `Crawl-delay: 5` for gallica.bnf.fr and enforces it there by banning an\n * address, so five seconds is the only figure the institution has stated about\n * how fast it wants to be read. Three seconds is the floor here because a\n * SPARQL query is one request where a crawl is thousands, and because every\n * answer is cached: the spacing that matters is the one between two distinct\n * questions.\n *\n * Configuration can slow the server down. It cannot take the spacing below this\n * floor, whether the setting arrives through the environment or through a\n * configuration object handed to the published client.\n */\nexport const MIN_ALLOWED_INTERVAL_MS = 3000;\n/** Beyond this a request would look hung rather than paced. */\nexport const MAX_ALLOWED_INTERVAL_MS = 120_000;\n/** The spacing in force when nothing asks for more. */\nexport const DEFAULT_INTERVAL_MS = 3000;\n\n/**\n * The deadline one query is given.\n *\n * Measured against the live endpoint, a lookup by identifier answers in well\n * under a second, while a full-text query over titles takes several seconds and\n * a query the planner handles badly is dropped by the server with an empty\n * body. Sixty seconds clears the slow honest case without holding the single\n * request slot for a query that will never answer.\n */\nexport const DEFAULT_TIMEOUT_MS = 60_000;\n\nexport interface Config {\n userAgent: string;\n minIntervalMs: number;\n timeoutMs: number;\n maxRetries: number;\n cacheTtlMs: number;\n cacheMaxEntries: number;\n logLevel: LogLevel;\n}\n\nexport const DEFAULT_USER_AGENT = `mcp-databnf/${PKG_VERSION} (+${REPO_URL})`;\n\nexport interface Logger {\n debug: (message: string) => void;\n info: (message: string) => void;\n warn: (message: string) => void;\n error: (message: string) => void;\n}\n\nexport function createLogger(level: LogLevel): Logger {\n const rank = LOG_LEVELS.indexOf(level);\n const write = (at: LogLevel, message: string) => {\n if (rank === 0 || LOG_LEVELS.indexOf(at) > rank) {\n return;\n }\n process.stderr.write(`[mcp-databnf] ${at}: ${message}\\n`);\n };\n return {\n debug: (m) => write(\"debug\", m),\n info: (m) => write(\"info\", m),\n // A warning goes out at the error level so it survives the default\n // setting: a caller has to know that rows were dropped.\n warn: (m) => write(\"error\", m),\n error: (m) => write(\"error\", m),\n };\n}\n\nfunction readInteger(\n env: NodeJS.ProcessEnv,\n name: string,\n fallback: number,\n min: number,\n max: number,\n): number {\n const raw = env[name];\n if (raw === undefined || raw.trim() === \"\") {\n return fallback;\n }\n\n const value = Number(raw);\n if (!(Number.isFinite(value) && Number.isInteger(value))) {\n process.stderr.write(\n `[mcp-databnf] error: ${name}=\"${raw}\" is not a whole number; using ${fallback}.\\n`,\n );\n return fallback;\n }\n if (value < min || value > max) {\n // Clamping silently would let a caller believe a setting took effect when\n // the opposite is true, so the refusal is stated and the default stands.\n process.stderr.write(\n `[mcp-databnf] error: ${name}=${value} is outside ${min}..${max}; using ${fallback}.\\n`,\n );\n return fallback;\n }\n return value;\n}\n\nexport function loadConfig(env: NodeJS.ProcessEnv = process.env): Config {\n const level = env.BNF_LOG_LEVEL as LogLevel | undefined;\n const logLevel = level && LOG_LEVELS.includes(level) ? level : \"error\";\n if (level && !LOG_LEVELS.includes(level)) {\n process.stderr.write(\n `[mcp-databnf] error: BNF_LOG_LEVEL=\"${level}\" is not one of ${LOG_LEVELS.join(\", \")}; using error.\\n`,\n );\n }\n\n const custom = env.BNF_USER_AGENT?.trim();\n\n return {\n // A caller who wants to be recognised may say who they are, and the\n // contact address stays attached: the BnF has to be able to reach a human\n // about traffic it did not expect.\n userAgent: custom ? `${custom} ${DEFAULT_USER_AGENT}` : DEFAULT_USER_AGENT,\n minIntervalMs: readInteger(\n env,\n \"BNF_MIN_INTERVAL_MS\",\n DEFAULT_INTERVAL_MS,\n MIN_ALLOWED_INTERVAL_MS,\n MAX_ALLOWED_INTERVAL_MS,\n ),\n timeoutMs: readInteger(env, \"BNF_TIMEOUT_MS\", DEFAULT_TIMEOUT_MS, 1000, 300_000),\n maxRetries: readInteger(env, \"BNF_MAX_RETRIES\", 3, 0, 8),\n cacheTtlMs: readInteger(env, \"BNF_CACHE_TTL_MS\", 900_000, 0, 86_400_000),\n cacheMaxEntries: readInteger(env, \"BNF_CACHE_MAX_ENTRIES\", 200, 1, 5000),\n logLevel,\n };\n}\n","/**\n * Kept in step with the version in package.json. The User-Agent embeds it, so\n * the BnF can tell which build is calling from its own logs.\n */\nexport const PKG_VERSION = \"2.1.2\";\nexport const REPO_URL = \"https://github.com/smeet666/mcp-databnf\";\n","/**\n * One error type, carrying a code the caller can branch on.\n *\n * The distinction that matters most is between \"the BnF describes no such\n * thing\" and \"the question could not be asked\". Collapsing the two lets a model\n * report an absence it never established, which is a false statement about a\n * catalogue rather than a missing feature.\n */\n\nexport type ErrorCode =\n /** data.bnf.fr answered, and describes no such person, work or edition. */\n | \"not_found\"\n /** The arguments cannot produce a query. */\n | \"invalid_input\"\n /** data.bnf.fr asked this client to slow down or refused it for now. */\n | \"rate_limited\"\n /** An answer arrived in a shape this server cannot read. */\n | \"parse_failure\"\n /** The request could not be completed. */\n | \"network_error\"\n /** The request was abandoned before an answer arrived. */\n | \"timeout\";\n\nexport interface ErrorDetails {\n /** What the caller can do about it, when there is something. */\n hint?: string;\n /** The address that produced the failure, for a bug report. */\n url?: string;\n status?: number;\n /** What was raised underneath, kept for the bug report the hint asks for. */\n cause?: unknown;\n}\n\nexport class BnfError extends Error {\n readonly code: ErrorCode;\n readonly details: ErrorDetails;\n\n constructor(code: ErrorCode, message: string, details: ErrorDetails = {}) {\n super(message, details.cause === undefined ? undefined : { cause: details.cause });\n this.name = \"BnfError\";\n this.code = code;\n this.details = details;\n }\n}\n\nexport const notFound = (message: string, details?: ErrorDetails) =>\n new BnfError(\"not_found\", message, details);\n\nexport const invalidInput = (message: string, hint?: string) =>\n new BnfError(\"invalid_input\", message, hint ? { hint } : {});\n\nexport const rateLimited = (message: string, details?: ErrorDetails) =>\n new BnfError(\"rate_limited\", message, {\n hint: \"Wait a moment and ask again. This says nothing about whether the BnF describes what you asked for.\",\n ...details,\n });\n\nexport const parseFailure = (message: string, details?: ErrorDetails) =>\n new BnfError(\"parse_failure\", message, {\n hint: \"data.bnf.fr may have changed how it answers. Please report this at https://github.com/smeet666/mcp-databnf/issues with the arguments you used.\",\n ...details,\n });\n\nexport const networkError = (message: string, details?: ErrorDetails) =>\n new BnfError(\"network_error\", message, details);\n\nexport const timeout = (message: string, details?: ErrorDetails) =>\n new BnfError(\"timeout\", message, details);\n","/**\n * A small in-memory store, keyed by the query that produced the value.\n *\n * It exists to keep a conversation that asks the same thing twice from asking\n * the endpoint twice, which matters here because a conversation about one\n * author walks the same records repeatedly: the person, then the works, then\n * the editions of one work, then back to the person.\n *\n * Entries expire, the store is bounded, and it holds only what was successfully\n * read: storing a response nobody could parse would serve that failure back for\n * the rest of its lifetime.\n *\n * Each entry keeps the moment it was read. The licence asks for the date the\n * metadata was retrieved, and a cached answer was retrieved when it entered the\n * store rather than when it was served, so the stored moment is what travels\n * with it.\n */\n\ninterface Entry<T> {\n value: T;\n expiresAt: number;\n retrievedAt: number;\n}\n\nexport interface Hit<T> {\n value: T;\n retrievedAt: number;\n}\n\nexport class Cache<T> {\n private readonly entries = new Map<string, Entry<T>>();\n\n constructor(\n private readonly ttlMs: number,\n private readonly maxEntries: number,\n ) {}\n\n get(key: string): Hit<T> | undefined {\n const entry = this.entries.get(key);\n if (!entry) {\n return undefined;\n }\n if (entry.expiresAt <= Date.now()) {\n this.entries.delete(key);\n return undefined;\n }\n // Re-inserting marks it as the most recently used, which is what the\n // eviction below reads.\n this.entries.delete(key);\n this.entries.set(key, entry);\n return { value: entry.value, retrievedAt: entry.retrievedAt };\n }\n\n set(key: string, value: T, retrievedAt: number): void {\n // A lifetime of zero turns the store off rather than expiring at once:\n // nothing is written, so nothing has to be checked on the way out.\n if (this.ttlMs <= 0) {\n return;\n }\n this.entries.delete(key);\n this.entries.set(key, { value, expiresAt: Date.now() + this.ttlMs, retrievedAt });\n while (this.entries.size > this.maxEntries) {\n const oldest = this.entries.keys().next();\n if (oldest.done) {\n break;\n }\n this.entries.delete(oldest.value);\n }\n }\n\n get size(): number {\n return this.entries.size;\n }\n}\n","/**\n * Every host, address and vocabulary term this server reads, in one place. An\n * upstream rename is then a one-file change rather than a hunt through the\n * parsers.\n *\n * The robots file at data.bnf.fr withholds one path, the one ending in\n * `cross-documents`, and nothing built here reaches it. The endpoint below is\n * addressed without a trailing slash: with one, the same address serves a\n * JavaScript query editor rather than the query service.\n */\n\nimport { BnfError } from \"../errors.js\";\n\n/** The root a host name may end on, which names the same host without it. */\nconst TRAILING_DOT = /\\.$/;\nconst FRAGMENT = /#.*$/;\nconst HTTP_SCHEME = /^http:/;\n\n/** The one address this server sends a request to. */\nexport const SPARQL_ENDPOINT = \"https://data.bnf.fr/sparql\";\n\n/** The host every request must be addressed to, checked before each call. */\nexport const ALLOWED_HOST = \"data.bnf.fr\";\n\n/**\n * The host this server describes and never requests.\n *\n * The BnF places its metadata and its digitised contents under two different\n * regimes. The metadata served here is free to reuse with its source and its\n * date of retrieval named. The contents on gallica.bnf.fr are not: their terms\n * make use inside an artificial-intelligence project subject to a paid licence\n * outside academic research, and the site refuses ClaudeBot and GPTBot at the\n * server, then bans the calling address after about fifteen requests whatever\n * the pace.\n *\n * So a Gallica address is a piece of metadata here, rendered as a link for a\n * person to open. `assertRequestable` below is what keeps that a rule rather\n * than an intention.\n */\nexport const GALLICA_HOST = \"gallica.bnf.fr\";\n\n/**\n * Refuse to request anything but the query service.\n *\n * This sits between the query builders and `fetch`, so a future route, a\n * redirect followed by hand, or an address read out of a response cannot become\n * a request to a host this server has no licence to read.\n */\nexport function assertRequestable(url: string): void {\n let parsed: URL;\n try {\n parsed = new URL(url);\n } catch (cause) {\n throw refusal(`mcp-databnf refuses to request \"${url}\": it is not an address.`, cause);\n }\n const host = parsed.hostname.toLowerCase().replace(TRAILING_DOT, \"\");\n if (host !== ALLOWED_HOST) {\n throw refusal(\n `mcp-databnf requests ${ALLOWED_HOST} and nothing else; \"${host}\" was refused. ` +\n \"Gallica in particular is described from the catalogue and never read.\",\n );\n }\n}\n\n/**\n * The refusal, raised as an error the transport will not retry.\n *\n * A plain error here would be read as a request that failed to complete, and the\n * next attempt would be made against the same address. A decision not to call a\n * host is settled, so it has to arrive as an answer rather than as a mishap.\n */\nconst refusal = (message: string, cause?: unknown) =>\n new BnfError(\"network_error\", message, {\n hint: \"This server reads the BnF catalogue at data.bnf.fr. It holds no licence to read the digitised contents, so it does not follow an address that leaves that host.\",\n cause,\n });\n\n/** True when an address points at Gallica, whatever the scheme or the case. */\nexport function isGallicaAddress(address: string): boolean {\n try {\n // A trailing dot names the root of the domain tree and resolves to the same\n // host. Comparing without stripping it would read a Gallica address as\n // somewhere else, and the link would be dropped from the answer.\n const host = new URL(address).hostname.toLowerCase().replace(TRAILING_DOT, \"\");\n return host === GALLICA_HOST || host.endsWith(`.${GALLICA_HOST}`);\n } catch {\n return false;\n }\n}\n\n/** Namespaces the queries use, emitted as a PREFIX block. */\nexport const PREFIXES = {\n foaf: \"http://xmlns.com/foaf/0.1/\",\n rdf: \"http://www.w3.org/1999/02/22-rdf-syntax-ns#\",\n rdfs: \"http://www.w3.org/2000/01/rdf-schema#\",\n owl: \"http://www.w3.org/2002/07/owl#\",\n skos: \"http://www.w3.org/2004/02/skos/core#\",\n dcterms: \"http://purl.org/dc/terms/\",\n bio: \"http://vocab.org/bio/0.1/\",\n bnf: \"http://data.bnf.fr/ontology/bnf-onto/\",\n bibo: \"http://purl.org/ontology/bibo/\",\n isni: \"http://isni.org/ontology#\",\n /** RDA group 2 elements, which carry the biographical fields. */\n rdag2: \"http://rdvocab.info/ElementsGr2/\",\n /** RDA elements, which carry the bibliographic fields. */\n rdae: \"http://rdvocab.info/Elements/\",\n /** RDA relationships between work, expression, manifestation and item. */\n rdarel: \"http://rdvocab.info/RDARelationshipsWEMI/\",\n /** The FRBR classes data.bnf.fr types its entities with. */\n rdafrbr: \"http://rdvocab.info/uri/schema/FRBRentitiesRDA/\",\n} as const;\n\nexport const PREFIX_BLOCK = Object.entries(PREFIXES)\n .map(([name, iri]) => `PREFIX ${name}: <${iri}>`)\n .join(\"\\n\");\n\n/** The two shapes an entity address takes in this dataset. */\nexport const ARK_BASE = \"http://data.bnf.fr/ark:/12148/\";\nexport const TEMP_WORK_BASE = \"http://data.bnf.fr/temp-work/\";\n\n/** The page a person can open for a record, which every result carries. */\nexport const publicPageFor = (entityIri: string): string =>\n entityIri.replace(FRAGMENT, \"\").replace(HTTP_SCHEME, \"https:\");\n\n/** The BnF general catalogue, where a record is described for a reader. */\nexport const catalogueUrlFor = (arkId: string): string =>\n `https://catalogue.bnf.fr/ark:/12148/${arkId}`;\n\n/**\n * The licence this dataset is published under, and what it asks for.\n *\n * The BnF states it in one sentence: the metadata may be used freely and at no\n * cost, provided the source is named and the date of retrieval is stated. Both\n * halves are therefore attached to every answer rather than left to a caller.\n */\nexport const LICENCE_FR =\n \"L'utilisation de ces métadonnées est libre et gratuite sous réserve du maintien de la mention de leur source et de l'indication de leur date de récupération.\";\n\nexport const LICENCE_EN =\n \"These metadata may be used freely and at no cost, provided their source is named and the date they were retrieved is stated.\";\n","/**\n * One query sent to the endpoint, with a deadline and bounded retries.\n *\n * Two things separate a retry worth making from one that only adds load. A\n * refusal that carries a time to come back is obeyed rather than guessed at,\n * and an answer the service meant is never retried: asking again for something\n * that is not there wastes a request and delays the honest answer.\n *\n * The query travels in the body of a POST. A long query in a query string is\n * refused by an intermediary before it reaches the service, and a POST keeps the\n * text out of every access log between here and there.\n */\n\nimport {\n invalidInput,\n networkError,\n parseFailure,\n rateLimited,\n timeout as timeoutError,\n} from \"../errors.js\";\nimport type { Logger } from \"../config.js\";\nimport { SPARQL_ENDPOINT, assertRequestable } from \"./endpoint.js\";\nimport type { RateLimiter } from \"./rateLimiter.js\";\n\n/** The shape the endpoint's own engine writes a refusal in. */\nconst VIRTUOSO_ERROR = /^Virtuoso\\s+(\\w+)\\s+Error\\s+([\\s\\S]*)$/;\n\n/** The JSON shape a SPARQL SELECT answers with. */\nexport interface SparqlTerm {\n type: \"uri\" | \"literal\" | \"typed-literal\" | \"bnode\";\n value: string;\n datatype?: string;\n \"xml:lang\"?: string;\n}\n\nexport type SparqlRow = Record<string, SparqlTerm | undefined>;\n\nexport interface SparqlResults {\n head: { vars?: string[] };\n results: { bindings: SparqlRow[] };\n}\n\nexport interface QueryOptions {\n query: string;\n userAgent: string;\n timeoutMs: number;\n maxRetries: number;\n limiter: RateLimiter;\n logger: Logger;\n fetchImpl?: typeof fetch;\n}\n\n/** Statuses worth another attempt: the service is busy, not answering \"no\". */\nconst RETRYABLE = new Set([408, 425, 429, 500, 502, 503, 504]);\n/**\n * Statuses that mean the service is refusing for now and naming a delay.\n *\n * These produce a `rate_limited` error and honour a `Retry-After` header. A\n * server error is a different claim, so it keeps its own code even though it\n * widens the spacing the same way.\n */\nconst PUSH_BACK = new Set([429, 503]);\n\n/**\n * The longest wait worth taking rather than reporting.\n *\n * A refusal may name any delay, and an hour is a legal answer. Sleeping through\n * it holds the one request slot this server has, so every other tool waits\n * behind a call whose caller has long since given up. Past this point the wait\n * is the answer, and the caller decides what to do with it.\n */\nconst LONGEST_WAIT_MS = 30_000;\n\n/**\n * How many times a query that never answered is worth repeating.\n *\n * A query that did not respond within its budget is one the planner is\n * struggling with. Repeating it adds the same load again, and each attempt\n * holds the slot for the full deadline.\n */\nconst RETRIES_AFTER_SILENCE = 1;\n\n/**\n * Redirects worth following before calling it a loop.\n *\n * The query service answers directly, so one hop covers a host that has moved\n * and two cover a scheme change on top of it. Beyond that the answer is a\n * redirect rather than a result.\n */\nconst MAX_REDIRECTS = 2;\n\n/**\n * Read a Retry-After header, which is either a number of seconds or a date.\n * Returns null when it says neither, so the caller falls back to its own wait.\n */\nexport function parseRetryAfter(value: string | null, now = Date.now()): number | null {\n if (!value) {\n return null;\n }\n const trimmed = value.trim();\n\n const seconds = Number(trimmed);\n if (Number.isFinite(seconds) && seconds >= 0) {\n return Math.round(seconds * 1000);\n }\n\n const at = Date.parse(trimmed);\n if (Number.isNaN(at)) {\n return null;\n }\n return Math.max(0, at - now);\n}\n\n/** Growing wait with jitter, so several clients do not return in step. */\nfunction backoffMs(attempt: number): number {\n const base = Math.min(16_000, 1000 * 2 ** attempt);\n return base + Math.floor(Math.random() * 500);\n}\n\n/**\n * What a Virtuoso says when it will not run a query.\n *\n * It answers in plain text rather than in the result format, and the leading\n * code says which half went wrong: a compiler code means the query as written\n * could not be understood, and a runtime code means it was understood and then\n * abandoned, usually because it asked for more of the graph than the service\n * will spend on one caller.\n */\nexport function readEngineError(\n body: string,\n): { kind: \"compile\" | \"runtime\"; text: string } | null {\n const match = VIRTUOSO_ERROR.exec(body.trim());\n if (!match) {\n return null;\n }\n const state = match[1] ?? \"\";\n const text = (match[2] ?? \"\").split(\"\\n\")[0]?.trim() ?? \"\";\n // 37000 is the compiler's own state; the rest come from execution.\n return { kind: state === \"37000\" ? \"compile\" : \"runtime\", text };\n}\n\nasync function postOnce(\n options: QueryOptions,\n signal: AbortSignal,\n): Promise<{ status: number; headers: Headers; body: string; ok: boolean }> {\n assertRequestable(SPARQL_ENDPOINT);\n const doFetch = options.fetchImpl ?? fetch;\n\n const form = new URLSearchParams();\n form.set(\"query\", options.query);\n // The endpoint answers in XML when only the Accept header asks for JSON, so\n // the format is stated in the request itself as well.\n form.set(\"format\", \"application/sparql-results+json\");\n\n // Redirects are handled here rather than by fetch. Following one automatically\n // would take the request to whatever host the answer names, and the guard\n // above only ever sees the address this file wrote: a redirect to Gallica\n // would cross the licence boundary with no code having decided to.\n let target = SPARQL_ENDPOINT;\n for (let hop = 0; hop <= MAX_REDIRECTS; hop += 1) {\n const response = await doFetch(target, {\n method: \"POST\",\n signal,\n redirect: \"manual\",\n headers: {\n \"user-agent\": options.userAgent,\n accept: \"application/sparql-results+json\",\n \"content-type\": \"application/x-www-form-urlencoded\",\n },\n body: form.toString(),\n });\n\n const location = response.headers.get(\"location\");\n if (response.status >= 300 && response.status < 400 && location !== null) {\n const next = new URL(location, target).toString();\n // Checked before the next request rather than after it, so a refused\n // address is one this client never called.\n assertRequestable(next);\n target = next;\n continue;\n }\n\n return {\n status: response.status,\n headers: response.headers,\n body: await response.text(),\n ok: response.ok,\n };\n }\n\n throw networkError(`data.bnf.fr redirected this request more than ${MAX_REDIRECTS} times.`, {\n url: SPARQL_ENDPOINT,\n });\n}\n\n/** What a refusal from the endpoint amounts to, and what it costs the pacing. */\ntype Refusal =\n | { kind: \"refused\"; error: Error; pushBack: boolean }\n | { kind: \"again\"; waitMs: number; pushBack: boolean };\n\n/**\n * Read a status the endpoint answered with, apart from the loop that retries.\n *\n * Anything worth another attempt means the service is struggling, so the gap\n * widens whether or not the status is one that names a delay: a Virtuoso under\n * load answers 500 as readily as 503. A status the service read the query for\n * and would not run is not a network failure, and calling it one invites a\n * retry of something only the query can fix.\n */\nfunction readRefusal(\n response: { status: number; body: string; headers: Headers },\n attempt: number,\n maxRetries: number,\n): Refusal {\n const pushBack = RETRYABLE.has(response.status);\n\n if (PUSH_BACK.has(response.status)) {\n const asked = parseRetryAfter(response.headers.get(\"retry-after\"));\n\n if (asked !== null && asked > LONGEST_WAIT_MS) {\n return {\n kind: \"refused\",\n pushBack,\n error: rateLimited(\n `data.bnf.fr asked this client to wait ${Math.round(asked / 1000)} seconds (HTTP ${response.status}).`,\n { url: SPARQL_ENDPOINT, status: response.status },\n ),\n };\n }\n if (attempt >= maxRetries) {\n return {\n kind: \"refused\",\n pushBack,\n error: rateLimited(\n `data.bnf.fr asked this client to slow down (HTTP ${response.status}).`,\n {\n url: SPARQL_ENDPOINT,\n status: response.status,\n },\n ),\n };\n }\n return { kind: \"again\", pushBack, waitMs: asked ?? backoffMs(attempt) };\n }\n\n if (RETRYABLE.has(response.status) && attempt < maxRetries) {\n return { kind: \"again\", pushBack, waitMs: backoffMs(attempt) };\n }\n\n if (response.status === 400 || response.status === 422) {\n const engine = readEngineError(response.body);\n return {\n kind: \"refused\",\n pushBack,\n error: invalidInput(\n `data.bnf.fr would not run this query${engine?.text ? `: ${engine.text}` : \".\"}`,\n \"Try fewer or plainer words. If this keeps happening with ordinary input, please report it.\",\n ),\n };\n }\n\n return {\n kind: \"refused\",\n pushBack,\n error: networkError(`data.bnf.fr answered HTTP ${response.status}.`, {\n url: SPARQL_ENDPOINT,\n status: response.status,\n }),\n };\n}\n\n/**\n * What a thrown attempt amounts to, or the error it has become.\n *\n * An error this module raised on purpose already says what happened, and is\n * passed straight on. Silence is given fewer attempts than a refusal: a query\n * spanning the whole catalogue can take longer than the service will spend on\n * it, and asking again costs both sides the same wait.\n */\nfunction readFailure(\n error: unknown,\n attempts: { attempt: number; maxRetries: number; timeoutMs: number },\n): Error {\n const { attempt, maxRetries, timeoutMs } = attempts;\n\n if (error instanceof Error && error.name === \"BnfError\") {\n throw error;\n }\n\n if (error instanceof Error && error.name === \"AbortError\") {\n if (attempt >= Math.min(maxRetries, RETRIES_AFTER_SILENCE)) {\n throw timeoutError(\n `No answer from data.bnf.fr within ${timeoutMs}ms. A query spanning the whole catalogue can take longer than the service will spend on it.`,\n { url: SPARQL_ENDPOINT },\n );\n }\n return error;\n }\n\n const failure = error instanceof Error ? error : new Error(String(error));\n if (attempt >= maxRetries) {\n throw networkError(`Could not reach data.bnf.fr: ${failure.message}`, { url: SPARQL_ENDPOINT });\n }\n return failure;\n}\n\n/** Send one query and return the parsed result set. */\nexport async function runQuery(options: QueryOptions): Promise<SparqlResults> {\n const { timeoutMs, maxRetries, limiter, logger } = options;\n\n let lastError: Error | null = null;\n /** Honoured before the next attempt rather than slept after the last one. */\n let askedWaitMs = 0;\n\n for (let attempt = 0; attempt <= maxRetries; attempt += 1) {\n if (askedWaitMs > 0) {\n logger.debug(`waiting ${askedWaitMs}ms, as asked`);\n // Read once into a constant: the timer closes over what this attempt was\n // told to wait, not over whatever a later attempt puts there.\n const asked = askedWaitMs;\n await new Promise((resolve) => setTimeout(resolve, asked));\n askedWaitMs = 0;\n }\n await limiter.beforeRequest();\n\n const controller = new AbortController();\n const deadline = setTimeout(() => controller.abort(), timeoutMs);\n\n try {\n logger.debug(`POST ${SPARQL_ENDPOINT} (${options.query.length} characters)`);\n const response = await postOnce(options, controller.signal);\n\n if (response.ok) {\n // The body decides whether this was an answer. This endpoint says \"I\n // will spend no more on you\" with a status of 200 and either an empty\n // body or a runtime error in plain text, so crediting the status line\n // would narrow the spacing at the one moment it has to widen.\n let parsed: SparqlResults;\n try {\n parsed = parseResults(response.body);\n } catch (error) {\n limiter.pushBack();\n throw error;\n }\n limiter.succeeded();\n return parsed;\n }\n\n const verdict = readRefusal(response, attempt, maxRetries);\n if (verdict.pushBack) {\n limiter.pushBack();\n }\n if (verdict.kind === \"refused\") {\n throw verdict.error;\n }\n askedWaitMs = verdict.waitMs;\n lastError = new Error(`HTTP ${response.status}`);\n } catch (error) {\n clearTimeout(deadline);\n\n lastError = readFailure(error, { attempt, maxRetries, timeoutMs });\n askedWaitMs = backoffMs(attempt);\n } finally {\n clearTimeout(deadline);\n }\n }\n\n throw networkError(\n `Could not reach data.bnf.fr: ${lastError?.message ?? \"no attempt was made\"}`,\n {\n url: SPARQL_ENDPOINT,\n },\n );\n}\n\n/**\n * Read a result set out of a body that arrived with a success status.\n *\n * The endpoint answers 200 with an empty body when it abandons a query part way\n * through, which is the same silence as a result set holding no rows and means\n * the opposite. An empty body is therefore a failure to read rather than an\n * absence, so nothing downstream can report \"the BnF describes none\" on the\n * strength of a query that was never finished.\n */\nexport function parseResults(body: string): SparqlResults {\n if (body.trim() === \"\") {\n throw parseFailure(\n \"data.bnf.fr returned an empty body, which is what it does when it abandons a query part way through.\",\n { url: SPARQL_ENDPOINT },\n );\n }\n\n const engine = readEngineError(body);\n if (engine) {\n if (engine.kind === \"compile\") {\n throw invalidInput(\n `data.bnf.fr could not read this query: ${engine.text}`,\n \"Try fewer or plainer words. If this keeps happening with ordinary input, please report it.\",\n );\n }\n throw timeoutError(`data.bnf.fr started this query and gave it up: ${engine.text}`, {\n url: SPARQL_ENDPOINT,\n });\n }\n\n let payload: unknown;\n try {\n payload = JSON.parse(body);\n } catch (cause) {\n throw parseFailure(\"data.bnf.fr answered with something that is not JSON.\", {\n url: SPARQL_ENDPOINT,\n cause,\n });\n }\n\n if (\n typeof payload !== \"object\" ||\n payload === null ||\n !(\"results\" in payload) ||\n typeof (payload as SparqlResults).results !== \"object\" ||\n !Array.isArray((payload as SparqlResults).results?.bindings)\n ) {\n throw parseFailure(\"data.bnf.fr answered with JSON that is not a SPARQL result set.\", {\n url: SPARQL_ENDPOINT,\n });\n }\n\n return payload as SparqlResults;\n}\n","/**\n * How caller text becomes part of a query, and what it can never become.\n *\n * A query is a program, and every character of it that came from outside is a\n * chance to write a different program. Three separate defences apply here, and\n * each is narrower than the one before:\n *\n * 1. Text that reaches the full-text index is reduced to words. Letters,\n * digits, apostrophes inside a word and hyphens inside a word survive; every\n * quote, brace, backslash, angle bracket and newline is dropped before the\n * query exists. There is no escaping to get right because there is nothing\n * left to escape.\n * 2. An identifier is matched against the two shapes this dataset uses and\n * rebuilt from the match, so the address in the query is one this file\n * wrote rather than one a caller supplied.\n * 3. Numbers reach the query as numbers, after being bounded.\n *\n * `escapeLiteral` exists for the values that are neither: it is the last line\n * rather than the first, and the tests hold it to producing a literal that\n * closes where it opened whatever it is handed.\n */\n\nimport { invalidInput } from \"../errors.js\";\n\n/** What a term can carry: letters, digits, the joiners and the spaces between them. */\nconst KEPT_CHARACTER = /[\\p{L}\\p{N}'\\-\\s]/u;\nconst TYPOGRAPHIC_APOSTROPHE = /[‘’ʼ]/u;\nconst LEADING_JOINERS = /^['-]+/u;\nconst TRAILING_JOINERS = /['-]+$/u;\nconst JOINERS = /['-]+/u;\nconst ARK_IN_INPUT = /(?:^|\\/)ark:\\/12148\\/(c[bc][0-9a-z]+)/i;\nconst LEADING_HASH = /^#/;\nconst FRAGMENT_OR_QUERY = /[#?]/;\nconst TEMP_WORK_IN_INPUT = /temp-work\\/([0-9a-f]{32})/i;\nconst GALLICA_ARK = /ark:\\/12148\\/([0-9a-z]+)/i;\n\n/**\n * Escape a string for a SPARQL short literal in double quotes.\n *\n * Backslash goes first: escaping it after the others would escape the\n * backslashes they just introduced. The characters a SPARQL short literal\n * cannot carry raw are a quote, a backslash, a line feed and a carriage\n * return; the rest are legal and pass through as published, since mangling a\n * title is its own kind of lie.\n */\nexport function escapeLiteral(value: string): string {\n return (\n value\n .replace(/\\\\/g, \"\\\\\\\\\")\n .replace(/\"/g, '\\\\\"')\n .replace(/\\n/g, \"\\\\n\")\n .replace(/\\r/g, \"\\\\r\")\n .replace(/\\t/g, \"\\\\t\")\n // The remaining control characters are legal inside a SPARQL literal and\n // meaningless in a catalogue query. A null byte in particular is where a\n // C string ends, so an intermediary or the engine can truncate the query\n // at one and run a shorter query than the one that was written.\n .replace(/[\\u0000-\\u0008\\u000b\\u000c\\u000e-\\u001f\\u007f]/gu, \"\")\n );\n}\n\n/** A literal, quoted and escaped, ready to be dropped into a query. */\nexport const literal = (value: string): string => `\"${escapeLiteral(value)}\"`;\n\n/**\n * Characters that occupy a position in a string and mark nothing on a screen.\n *\n * The control characters other than the whitespace ones, the zero-width spaces\n * and joiners, the soft hyphen, the bidirectional marks and the byte order\n * mark. A space, a tab and a line break are absent from the list: they are\n * visible as a gap and they do separate two words.\n *\n * They are written as escapes because they would be invisible in this file as\n * well, and a character nobody can see in the source of a guard is a guard\n * nobody can review.\n */\nconst INVISIBLE =\n /[\\u0000-\\u0008\\u000e-\\u001f\\u007f-\\u009f\\u00ad\\u061c\\u180e\\u200b-\\u200f\\u202a-\\u202e\\u2060-\\u2064\\u2066-\\u2069\\ufeff]/gu;\n\n/**\n * The text with the characters that mark nothing removed.\n *\n * A separator is something a person wrote to hold two words apart. One of these\n * holds nothing apart, so treating it as a separator turns one word into two\n * terms the index then requires separately, and a name that matches comes back\n * as a name the catalogue does not hold.\n */\nexport function withoutInvisibles(input: string): string {\n return input.replace(INVISIBLE, \"\");\n}\n\n/** The characters that marked nothing, listed once each, in the order written. */\nexport function invisibleCharacters(input: string): string[] {\n const found: string[] = [];\n for (const character of input.match(INVISIBLE) ?? []) {\n if (!found.includes(character)) {\n found.push(character);\n }\n }\n return found;\n}\n\n/**\n * The visible characters a search dropped, listed once each.\n *\n * Punctuation reaches no index here, so a caller comparing what they wrote\n * against what was searched for needs to see which marks were left out. A\n * space is not one of them: it separates words and is what the words are cut\n * along.\n */\nexport function charactersSetAside(input: string): string[] {\n const found: string[] = [];\n for (const character of withoutInvisibles(input).normalize(\"NFC\")) {\n if (KEPT_CHARACTER.test(character) || TYPOGRAPHIC_APOSTROPHE.test(character)) {\n continue;\n }\n if (!found.includes(character)) {\n found.push(character);\n }\n }\n return found;\n}\n\n/** How a piece of caller text became the terms the index was given. */\nexport interface SearchReading {\n /** The words the text was cut into, each one quoted into the query. */\n words: string[];\n /** The terms the index requires, after it splits inside a word. */\n terms: string[];\n /** The characters that marked nothing and were removed before the cutting. */\n invisible: string[];\n /** The visible characters that reached no term. */\n setAside: string[];\n}\n\n/** Read a piece of caller text the way the query builder and the index will. */\nexport function readSearchText(input: string): SearchReading {\n const words = toSearchWords(input);\n return {\n words,\n terms: toIndexTerms(words),\n invisible: invisibleCharacters(input),\n setAside: charactersSetAside(input),\n };\n}\n\n/**\n * Words a full-text query may carry.\n *\n * The endpoint is a Virtuoso, whose `bif:contains` takes its own miniature\n * language: words in single quotes joined by AND, OR and NOT, with `*` for a\n * prefix. Handing caller text to that language directly would let a caller\n * write the operators, so the text is cut into words instead and this file\n * writes every operator.\n *\n * A word keeps letters and digits in any script, and keeps an apostrophe or a\n * hyphen that sits between two other characters, because \"aujourd'hui\" and\n * \"Charleville-Mézières\" are one word each. A leading or trailing one is\n * dropped, so nothing can close the quoting this file opens.\n *\n * The same rule holds a span of years together: \"1871-1933\" stays one word.\n * Nothing here can tell a range from a compound name, and splitting on the\n * chance that it is a range would break every hyphenated place in France.\n */\nexport function toSearchWords(input: string): string[] {\n const cleaned = withoutInvisibles(input)\n .normalize(\"NFC\")\n // Anything that is not a letter, a digit, an apostrophe or a hyphen becomes\n // a separator. Typographic apostrophes are folded onto the plain one first,\n // since a name is written with either.\n .replace(/[‘’ʼ]/gu, \"'\")\n .replace(/[^\\p{L}\\p{N}'-]+/gu, \" \");\n\n const words: string[] = [];\n for (const raw of cleaned.split(\" \")) {\n // Trim the joiners at the edges: they only mean something between letters.\n const word = raw.replace(LEADING_JOINERS, \"\").replace(TRAILING_JOINERS, \"\");\n if (word === \"\") {\n continue;\n }\n // A word this long is a paste rather than a word, and the index will not\n // match it anyway.\n words.push(word.length > 60 ? word.slice(0, 60) : word);\n if (words.length >= 12) {\n break;\n }\n }\n return words;\n}\n\n/**\n * The terms the index will require, read off the words a caller wrote.\n *\n * The index tokenises inside a quoted term: an apostrophe and a hyphen are\n * separators there, so `O'Brien` reaches it as two terms and a record written\n * `Pat O Brien` carries both and matches. Reporting the word back as one term\n * would leave a caller checking why a row is on the list with no way of getting\n * there from the fields shown.\n *\n * A piece repeated across words is kept once, since requiring a term twice is\n * requiring it.\n */\nexport function toIndexTerms(words: readonly string[]): string[] {\n const terms: string[] = [];\n for (const word of words) {\n for (const piece of word.split(JOINERS)) {\n if (piece !== \"\" && !terms.includes(piece)) {\n terms.push(piece);\n }\n }\n }\n return terms;\n}\n\n/**\n * A `bif:contains` argument requiring every word, built from words this file\n * quoted.\n *\n * The result is a SPARQL literal, so the double quotes belong to SPARQL and the\n * single quotes belong to the index. Two quotings are therefore in play, and\n * each has to be closed by the layer that opened it.\n *\n * The apostrophe kept inside a word is also the index's own quoting character,\n * so `rimbaud'OR'hugo` arrives as one word and emits `'rimbaud'OR'hugo'`, where\n * `OR` has escaped the quotes and become an operator. Doubling it puts it back\n * inside the term, which is how the index writes a literal quote. That is the\n * whole of the exposure: the word cannot reach the SPARQL layer as anything but\n * text, because `escapeLiteral` runs over the finished clause.\n */\nexport function containsAllWords(words: readonly string[]): string {\n if (words.length === 0) {\n throw invalidInput(\n \"No word to search for: the text held no letters or digits.\",\n \"Write the words to look for, such as a surname or a title.\",\n );\n }\n const clause = words.map((word) => `'${word.replace(/'/g, \"''\")}'`).join(\" AND \");\n return literal(clause);\n}\n\n/**\n * The two identifier shapes this dataset uses, and nothing else.\n *\n * An ARK identifier is `cb` followed by digits and one final check character,\n * which the BnF also writes as `cc…` on a few records. A provisional work is\n * named by a thirty-two character hexadecimal digest. Both are matched whole,\n * so an identifier reaches a query only after this file has rebuilt it.\n */\nconst ARK_ID = /^c[bc][0-9a-z]{6,20}$/;\nconst TEMP_WORK_ID = /^[0-9a-f]{32}$/;\n\nexport type EntityKind = \"ark\" | \"temp-work\";\n\nexport interface EntityId {\n kind: EntityKind;\n /** The bare identifier, as a person would quote it. */\n id: string;\n /** The address of the thing itself, which the queries bind to. */\n iri: string;\n /** The page a person can open. */\n pageUrl: string;\n}\n\n/**\n * Read an identifier a caller wrote, in any of the forms they see.\n *\n * Rows carry `http://data.bnf.fr/ark:/12148/cb119219976#about`, prose carries\n * `cb119219976`, and a browser carries `https://data.bnf.fr/ark:/12148/cb119219976`.\n * All three name one record. Anything else is refused rather than guessed at,\n * because an identifier built from a guess sends the next call to a record that\n * does not exist and the answer reads as an absence.\n */\nexport function parseEntityId(input: string): EntityId {\n const trimmed = input.trim();\n if (trimmed === \"\") {\n throw invalidInput(\"An identifier is required.\");\n }\n\n const arkMatch = ARK_IN_INPUT.exec(trimmed);\n const bare = arkMatch\n ? arkMatch[1]\n : trimmed.replace(LEADING_HASH, \"\").split(FRAGMENT_OR_QUERY)[0];\n const candidate = (bare ?? \"\").toLowerCase();\n\n if (ARK_ID.test(candidate)) {\n return {\n kind: \"ark\",\n id: candidate,\n iri: `http://data.bnf.fr/ark:/12148/${candidate}#about`,\n pageUrl: `https://data.bnf.fr/ark:/12148/${candidate}`,\n };\n }\n\n const tempMatch = TEMP_WORK_IN_INPUT.exec(trimmed);\n const digest = (tempMatch?.[1] ?? candidate).toLowerCase();\n if (TEMP_WORK_ID.test(digest)) {\n return {\n kind: \"temp-work\",\n id: `temp-work/${digest}`,\n iri: `http://data.bnf.fr/temp-work/${digest}/#about`,\n pageUrl: `https://data.bnf.fr/temp-work/${digest}/`,\n };\n }\n\n throw invalidInput(`\"${input}\" is not a data.bnf.fr identifier.`, hintFor(trimmed));\n}\n\n/**\n * Two shapes people paste, which are addresses of something else entirely.\n *\n * A Gallica document identifier and a data.bnf.fr page address both look like\n * identifiers to whoever is holding one, and both are refused here. Refusing\n * them with the general advice reads as the server failing to know a record it\n * plainly has, so each is named for what it is and told what to do instead.\n */\nconst GALLICA_DOCUMENT = /^(?:bpt6k|btv1b|bd6t|cb32|btv1b)[0-9a-z]+$/i;\n/** `https://data.bnf.fr/fr/11907966/marguerite_duras/`, which is what a browser shows. */\nconst PAGE_ADDRESS = /data\\.bnf\\.fr\\/(?:[a-z]{2}\\/)?(\\d{6,12})\\/([a-z0-9_]+)/i;\n\nconst GENERAL_HINT =\n \"An identifier looks like 'cb119219976' for an established record, or 'temp-work/22d7f68c1a4bdd081ad7ca791fd3b730' for a provisional one. Take it from a search result rather than building one.\";\n\nfunction hintFor(input: string): string {\n const page = PAGE_ADDRESS.exec(input);\n if (page) {\n const name = (page[2] ?? \"\").replace(/_/g, \" \");\n return (\n `That is the readable address of a data.bnf.fr page, and ${page[1]} in it is the record's FRBNF number rather than the identifier these tools take. ` +\n `Search for \"${name}\" with search_authors or search_works, and use the identifier the row carries.`\n );\n }\n\n const gallicaArk = GALLICA_ARK.exec(input)?.[1] ?? input;\n if (GALLICA_DOCUMENT.test(gallicaArk)) {\n return (\n \"That is a Gallica identifier: it names a digitised document rather than a record in the catalogue, and these tools read the catalogue. \" +\n \"Search for the work with search_works, then list_editions will give you the Gallica link alongside the edition it belongs to.\"\n );\n }\n\n return GENERAL_HINT;\n}\n\n/** An identifier written back as the address of the entity itself. */\nexport const iriOf = (id: EntityId): string => `<${id.iri}>`;\n\n/**\n * A whole number, bounded, written into a query.\n *\n * LIMIT and OFFSET take a bare number rather than a quoted value, so this is\n * the one place where something that is not a literal is assembled. Bounding\n * before writing is what makes that safe, and it also keeps a caller from\n * asking the endpoint for a page a hundred thousand rows deep.\n */\nexport function boundedInteger(value: number, min: number, max: number, name: string): number {\n if (!(Number.isFinite(value) && Number.isInteger(value))) {\n throw invalidInput(`'${name}' has to be a whole number.`);\n }\n if (value < min || value > max) {\n throw invalidInput(`'${name}' has to be between ${min} and ${max}.`);\n }\n return value;\n}\n","/**\n * The queries this server sends, and the reasons they are shaped as they are.\n *\n * Every one of them describes the endpoint as it actually answers: the shapes\n * below are the ones the live service returns, rather than the ones the\n * vocabularies promise.\n *\n * Three facts about this dataset govern most of what follows.\n *\n * **An entity and its record are two addresses.** A person is\n * `ark:/12148/cb119219976#about`, typed `foaf:Person`, and carries the dates,\n * the places and the alignments. The authority record is the same ARK without\n * the fragment, typed `skos:Concept`, and carries the headings, the editorial\n * notes and the date the record was last changed. A query that reads only one\n * of the two misses half the record.\n *\n * **Work, expression and manifestation are three levels.** A work links to\n * expressions, and a manifestation names both the expression it realises and\n * the work it manifests. `workManifested` is what makes the editions of a work\n * reachable in one step rather than two.\n *\n * **The full-text index drives, or the query is abandoned.** Virtuoso needs the\n * triple whose object it will search to appear before `bif:contains` in the same\n * group, and it needs that pattern to be the one that starts the plan. Asking\n * for `?w a rdafrbr:Work` first makes the planner walk every work in the\n * catalogue and the service gives the query up, answering 200 with an empty\n * body. Putting the text search in a subquery and filtering its output is what\n * keeps the index in front.\n *\n * **A paged query is ordered at both levels.** The subquery decides which\n * entities a page holds; the outer query decides the sequence its rows arrive\n * in, and SPARQL gives an unordered outer query no sequence at all. Since a\n * page is read by taking one entity beyond it and dropping the last, an outer\n * form without `ORDER BY` drops an entity chosen by the service: that entity is\n * then missing from the walk, and one of its neighbours is served on two pages.\n * Both levels therefore sort on the same variable, so the sequence a caller\n * reads is the sequence the pages were cut along. The outer sort runs over the\n * rows of one page, so it costs a sort of a few dozen rows rather than of the\n * matching set.\n */\n\nimport { PREFIX_BLOCK } from \"./endpoint.js\";\nimport type { EntityId } from \"./sparql.js\";\nimport { boundedInteger, containsAllWords, iriOf, literal } from \"./sparql.js\";\n\n/**\n * How many rows a text search reads from the index before filtering.\n *\n * `bif:contains` answers in index order, which is not an order of relevance, so\n * a window that is too small leaves out the record a caller meant. A window this\n * size costs the service one pass and gives the filter enough to work with.\n *\n * It is also the end of what a text search can page through. An offset past it\n * lands beyond what was ever read, and the answer would be an empty page that\n * looks exactly like the catalogue holding nothing. The tools refuse such a page\n * rather than buying the query, which is why this is exported.\n */\nexport const TEXT_WINDOW = 400;\n\nconst prefixed = (body: string): string => `${PREFIX_BLOCK}\\n${body.trim()}\\n`;\n\n/**\n * How many rows the window returned, asked for in the request that reads it.\n *\n * The window bounds the answer, and it is read before the type filter, so a\n * page of a few dozen rows can rest on a window that came back full. From the\n * page alone the two readings are identical: an index with nothing more to give\n * and an index holding a great deal more that was never asked. The occupancy is\n * what separates them, and it costs one extra pass of the same index inside a\n * request that was already being sent, rather than a second call.\n *\n * It arrives as a row of its own, binding the count and no entity, because a\n * branch of a UNION answers even when the other branch holds nothing. A page\n * emptied by the filter is the reading that most needs the fact.\n */\nfunction windowOccupancy(predicate: string, words: readonly string[]): string {\n return `\n {\n SELECT (COUNT(*) AS ?windowRows) WHERE {\n SELECT DISTINCT ?windowEntity ?windowText WHERE {\n ?windowEntity ${predicate} ?windowText .\n ?windowText bif:contains ${containsAllWords(words)} .\n }\n LIMIT ${TEXT_WINDOW}\n }\n }`;\n}\n\n/**\n * People whose name carries every word given.\n *\n * The name is searched rather than the authority heading, because a heading\n * carries the dates and a caller writing \"Arthur Rimbaud 1854\" would otherwise\n * match on the year. Both are returned, so an answer can show which of several\n * people bearing one name is which.\n *\n * `limit + 1` rows are asked for. The extra row is never shown; it is how the\n * answer can say that more exist without a second query counting them.\n */\nexport function searchAuthorsQuery(\n words: readonly string[],\n limit: number,\n offset: number,\n): string {\n const take = boundedInteger(limit, 1, 50, \"limit\") + 1;\n const skip = boundedInteger(offset, 0, 5000, \"offset\");\n\n // The page is cut in the middle subquery, over people rather than over rows,\n // and the cutting projection names the person alone. Selecting the name\n // alongside would give a person recorded under two names two of the page's\n // slots; the parser then folds them back into one row, and a page of ten\n // comes back holding eight.\n // The columns of the page are read inside the branch that produces it. Read\n // outside it, they would be joined against an unbound subject on the row the\n // occupancy arrives on, which asks the service for every name in the file.\n return prefixed(`\nSELECT ?person ?name ?label ?birthYear ?deathYear ?role ?windowRows WHERE {\n {\n {\n SELECT DISTINCT ?person WHERE {\n {\n SELECT DISTINCT ?person ?name WHERE {\n ?person foaf:name ?name .\n ?name bif:contains ${containsAllWords(words)} .\n }\n LIMIT ${TEXT_WINDOW}\n }\n ?person a foaf:Person .\n }\n ORDER BY ?person\n LIMIT ${take} OFFSET ${skip}\n }\n OPTIONAL { ?person foaf:name ?name }\n OPTIONAL { ?record foaf:focus ?person ; skos:prefLabel ?label }\n OPTIONAL { ?person bnf:firstYear ?birthYear }\n OPTIONAL { ?person bnf:lastYear ?deathYear }\n OPTIONAL { ?person rdag2:biographicalInformation ?role }\n }\n UNION${windowOccupancy(\"foaf:name\", words)}\n}\nORDER BY ?person`);\n}\n\n/**\n * The most triples one record's detail query will read.\n *\n * A record that hits this ceiling has been cut, and a cut record renders as a\n * record whose missing statements are absences. The parsers compare the row\n * count against it and say so rather than reporting the silence as fact.\n */\nexport const MOST_TRIPLES = 2000;\n\n/**\n * Everything both halves of one person's record carry.\n *\n * The two subjects are read in one query through a UNION rather than in two\n * requests: the spacing between requests is the cost that matters here, and one\n * person is one question.\n */\nexport function authorQuery(id: EntityId): string {\n const person = iriOf(id);\n return prefixed(`\nSELECT ?p ?o ?lang WHERE {\n {\n ${person} ?p ?o .\n BIND(LANG(?o) AS ?lang)\n }\n UNION\n {\n ?record foaf:focus ${person} ; ?p ?o .\n BIND(LANG(?o) AS ?lang)\n }\n}\nLIMIT ${MOST_TRIPLES}`);\n}\n\n/**\n * Works whose title carries every word given.\n *\n * Both established works and provisional ones are returned, and the row says\n * which is which: leaving the provisional ones out would hide the only record\n * the BnF holds for a great many recent books, and mixing them in silently\n * would let a provisional record be quoted as an established one.\n */\nexport function searchWorksQuery(words: readonly string[], limit: number, offset: number): string {\n const take = boundedInteger(limit, 1, 50, \"limit\") + 1;\n const skip = boundedInteger(offset, 0, 5000, \"offset\");\n\n // As with people, the page is cut over works rather than over rows, on the\n // work alone: a work with three authors occupies three rows, and a work\n // recorded under two titles would take two of the page's slots.\n return prefixed(`\nSELECT ?work ?title ?date ?status ?creator ?creatorName ?windowRows WHERE {\n {\n {\n SELECT DISTINCT ?work WHERE {\n {\n SELECT DISTINCT ?work ?title WHERE {\n ?work dcterms:title ?title .\n ?title bif:contains ${containsAllWords(words)} .\n }\n LIMIT ${TEXT_WINDOW}\n }\n ?work a rdafrbr:Work .\n }\n ORDER BY ?work\n LIMIT ${take} OFFSET ${skip}\n }\n OPTIONAL { ?work dcterms:title ?title }\n OPTIONAL { ?work dcterms:date ?date }\n OPTIONAL { ?work rdae:statusOfIdentification ?status }\n OPTIONAL {\n ?work dcterms:creator ?creator .\n OPTIONAL { ?creator foaf:name ?creatorName }\n }\n }\n UNION${windowOccupancy(\"dcterms:title\", words)}\n}\nORDER BY ?work`);\n}\n\n/**\n * The works one person is named the creator of.\n *\n * `dcterms:creator` is the statement that ties a work to the person who made\n * it, and it is the widest one at that level: every work reached through the\n * relator vocabularies is either reached through this one as well, or is a\n * record of a performance rather than of a work. So one statement answers the\n * question, and a second path would add rows of a different kind.\n *\n * The page is cut in a subquery over works alone, ordered by their address. A\n * work carrying two form codes arrives as two rows, and cutting the page over\n * rows would give that work two of the page's slots. The order is arbitrary and\n * it is stable, which is what a caller reading page after page needs.\n */\nexport function worksByCreatorQuery(id: EntityId, limit: number, offset: number): string {\n const person = iriOf(id);\n const take = boundedInteger(limit, 1, 50, \"limit\") + 1;\n const skip = boundedInteger(offset, 0, 5000, \"offset\");\n\n return prefixed(`\nSELECT ?work ?title ?date ?year ?status ?form WHERE {\n {\n SELECT DISTINCT ?work WHERE {\n ?work dcterms:creator ${person} .\n }\n ORDER BY ?work\n LIMIT ${take} OFFSET ${skip}\n }\n OPTIONAL { ?work dcterms:title ?title }\n OPTIONAL { ?work dcterms:date ?date }\n OPTIONAL { ?work bnf:firstYear ?year }\n OPTIONAL { ?work rdae:statusOfIdentification ?status }\n OPTIONAL { ?work rdae:formOfWork ?form }\n}\nORDER BY ?work`);\n}\n\n/** Everything one work's record carries, with the names of its creators. */\nexport function workQuery(id: EntityId): string {\n const work = iriOf(id);\n return prefixed(`\nSELECT ?p ?o ?lang ?name WHERE {\n ${work} ?p ?o .\n BIND(LANG(?o) AS ?lang)\n OPTIONAL { ?o foaf:name ?name }\n}\nLIMIT ${MOST_TRIPLES}`);\n}\n\n/**\n * The published editions of one work.\n *\n * A manifestation naming the same work twice, or carrying two digitised copies,\n * comes back as several rows describing one edition. The parser groups them,\n * which is why the manifestation's own address is selected first.\n */\nexport function editionsQuery(id: EntityId, limit: number, offset: number): string {\n const work = iriOf(id);\n const take = boundedInteger(limit, 1, 50, \"limit\") + 1;\n const skip = boundedInteger(offset, 0, 5000, \"offset\");\n\n return prefixed(`\nSELECT ?edition ?title ?date ?year ?publisher ?place ?editionStatement\n ?extent ?isbn ?note ?catalogue ?reproduction ?ocr WHERE {\n {\n SELECT DISTINCT ?edition WHERE {\n ?edition rdarel:workManifested ${work} .\n }\n ORDER BY ?edition\n LIMIT ${take} OFFSET ${skip}\n }\n OPTIONAL { ?edition dcterms:title ?title }\n OPTIONAL { ?edition dcterms:date ?date }\n OPTIONAL { ?edition bnf:firstYear ?year }\n OPTIONAL { ?edition rdae:publishersName ?publisher }\n OPTIONAL { ?edition rdae:placeOfPublication ?place }\n OPTIONAL { ?edition rdae:editionStatement ?editionStatement }\n OPTIONAL { ?edition dcterms:description ?extent }\n OPTIONAL { ?edition bnf:isbn ?isbn }\n OPTIONAL { ?edition rdae:note ?note }\n OPTIONAL { ?edition rdfs:seeAlso ?catalogue }\n OPTIONAL { ?edition rdarel:electronicReproduction ?reproduction }\n OPTIONAL { ?edition bnf:OCR ?ocr }\n}\nORDER BY ?edition`);\n}\n\n/**\n * Every digitised document one work's editions point at.\n *\n * Held apart from the edition listing because the two answer different\n * questions: one asks what was published, the other asks what can be opened.\n */\nexport function digitisedForWorkQuery(id: EntityId, limit: number): string {\n const work = iriOf(id);\n const take = boundedInteger(limit, 1, 200, \"limit\") + 1;\n\n return prefixed(`\nSELECT DISTINCT ?rank ?edition ?title ?reproduction ?ocr ?depiction WHERE {\n {\n ?edition rdarel:workManifested ${work} .\n OPTIONAL { ?edition dcterms:title ?title }\n { ?edition rdarel:electronicReproduction ?reproduction }\n UNION\n { ?edition bnf:OCR ?ocr }\n BIND(0 AS ?rank)\n }\n UNION\n {\n ${work} foaf:depiction ?depiction .\n BIND(${work} AS ?edition)\n BIND(1 AS ?rank)\n }\n}\nORDER BY ?rank\nLIMIT ${take}`);\n}\n\n/**\n * Every digitised document attached to one person.\n *\n * Two paths lead there and they mean different things. `foaf:depiction` hangs\n * off the person and illustrates them. A reproduction hangs off an edition of a\n * work they are credited with, and is that edition digitised. The parser keeps\n * the two apart, and the answer says which is which.\n *\n * Reproductions are ordered first because a well-known person carries dozens of\n * illustrations, and a page filled with those would answer \"what of this author\n * can be read\" with a list of pictures of him.\n */\nexport function digitisedForPersonQuery(id: EntityId, limit: number): string {\n const person = iriOf(id);\n const take = boundedInteger(limit, 1, 200, \"limit\") + 1;\n\n return prefixed(`\nSELECT DISTINCT ?rank ?edition ?title ?reproduction ?ocr ?depiction WHERE {\n {\n ?work dcterms:creator ${person} .\n ?edition rdarel:workManifested ?work .\n OPTIONAL { ?edition dcterms:title ?title }\n { ?edition rdarel:electronicReproduction ?reproduction }\n UNION\n { ?edition bnf:OCR ?ocr }\n BIND(0 AS ?rank)\n }\n UNION\n {\n ${person} foaf:depiction ?depiction .\n BIND(${person} AS ?edition)\n BIND(1 AS ?rank)\n }\n}\nORDER BY ?rank\nLIMIT ${take}`);\n}\n\n/**\n * Whether one address is described at all, and what it is.\n *\n * Asked when a lookup came back with nothing, so an answer can tell \"the BnF\n * describes no such record\" from \"that record exists and is not a person\".\n */\nexport function kindQuery(id: EntityId): string {\n return prefixed(`\nSELECT DISTINCT ?type WHERE {\n ${iriOf(id)} a ?type .\n}\nLIMIT 20`);\n}\n\n/**\n * A query written for the live suite alone, naming one record by its label.\n *\n * It exists so the canary exercises `literal` against text a person typed,\n * rather than only against words the tokeniser produced.\n */\nexport function labelProbeQuery(label: string): string {\n return prefixed(`\nSELECT ?person WHERE {\n ?person foaf:name ${literal(label)} .\n}\nLIMIT 3`);\n}\n","/**\n * Turning result rows into the shapes the tools render.\n *\n * The rule the whole file follows: a statement the record does not carry comes\n * out as `null` or as an empty list, never as a plausible value. A person with\n * no recorded year of death is alive, or unresearched, or died in a year nobody\n * wrote down, and the three are different claims. Only the record decides.\n */\n\nimport { isGallicaAddress, publicPageFor } from \"./endpoint.js\";\nimport { MOST_TRIPLES, TEXT_WINDOW } from \"./queries.js\";\nimport type { SparqlResults, SparqlRow, SparqlTerm } from \"./http.js\";\nimport type {\n AuthorDetail,\n AuthorSummary,\n AuthoredWork,\n DigitisedLink,\n Edition,\n Page,\n WorkDetail,\n WorkSummary,\n} from \"../types.js\";\n\nconst ARK_IN_IRI = /ark:\\/12148\\/(c[bc][0-9a-z]+)/i;\nconst TEMP_WORK_IN_IRI = /temp-work\\/([0-9a-f]{32})/i;\nconst TRAILING_SEPARATOR = /[/#]$/;\nconst PATH_SEPARATOR = /[/#]/;\nconst ARK_SEGMENT = /ark:\\/12148\\/([^/?#]+)/;\nconst QUERY_START = /[?#]/;\nconst LEADING_DOTS = /^[./]+/;\nconst TRAILING_SLASHES = /\\/+$/;\n\n/** Predicates read off a record. Named once, so a rename is one edit. */\nconst P = {\n type: \"http://www.w3.org/1999/02/22-rdf-syntax-ns#type\",\n label: \"http://www.w3.org/2000/01/rdf-schema#label\",\n seeAlso: \"http://www.w3.org/2000/01/rdf-schema#seeAlso\",\n sameAs: \"http://www.w3.org/2002/07/owl#sameAs\",\n exactMatch: \"http://www.w3.org/2004/02/skos/core#exactMatch\",\n closeMatch: \"http://www.w3.org/2004/02/skos/core#closeMatch\",\n prefLabel: \"http://www.w3.org/2004/02/skos/core#prefLabel\",\n altLabel: \"http://www.w3.org/2004/02/skos/core#altLabel\",\n created: \"http://purl.org/dc/terms/created\",\n modified: \"http://purl.org/dc/terms/modified\",\n title: \"http://purl.org/dc/terms/title\",\n date: \"http://purl.org/dc/terms/date\",\n creator: \"http://purl.org/dc/terms/creator\",\n language: \"http://purl.org/dc/terms/language\",\n subject: \"http://purl.org/dc/terms/subject\",\n name: \"http://xmlns.com/foaf/0.1/name\",\n givenName: \"http://xmlns.com/foaf/0.1/givenName\",\n familyName: \"http://xmlns.com/foaf/0.1/familyName\",\n depiction: \"http://xmlns.com/foaf/0.1/depiction\",\n birth: \"http://vocab.org/bio/0.1/birth\",\n death: \"http://vocab.org/bio/0.1/death\",\n firstYear: \"http://data.bnf.fr/ontology/bnf-onto/firstYear\",\n lastYear: \"http://data.bnf.fr/ontology/bnf-onto/lastYear\",\n subjectLabel: \"http://data.bnf.fr/ontology/bnf-onto/subject\",\n isni: \"http://isni.org/ontology#identifierValid\",\n placeOfBirth: \"http://rdvocab.info/ElementsGr2/placeOfBirth\",\n placeOfDeath: \"http://rdvocab.info/ElementsGr2/placeOfDeath\",\n biographicalInformation: \"http://rdvocab.info/ElementsGr2/biographicalInformation\",\n languageOfThePerson: \"http://rdvocab.info/ElementsGr2/languageOfThePerson\",\n countryAssociated: \"http://rdvocab.info/ElementsGr2/countryAssociatedWithThePerson\",\n fieldOfActivity: \"http://rdvocab.info/ElementsGr2/fieldOfActivityOfThePerson\",\n formOfWork: \"http://rdvocab.info/Elements/formOfWork\",\n statusOfIdentification: \"http://rdvocab.info/Elements/statusOfIdentification\",\n /** The occupation, which RDA numbers rather than names. */\n occupation: \"http://rdaregistry.info/Elements/a/#P50113\",\n /** Expressions of a work, which are not editions. */\n expressionOfWork: \"http://rdaregistry.info/Elements/w/#P10078\",\n} as const;\n\n/** Files whose addresses this dataset aligns records with. */\nconst ALIGNMENT_HOSTS: Array<{ host: string; name: string }> = [\n { host: \"viaf.org\", name: \"viaf\" },\n { host: \"idref.fr\", name: \"idref\" },\n { host: \"dbpedia.org\", name: \"dbpedia\" },\n { host: \"wikidata.org\", name: \"wikidata\" },\n { host: \"isni.org\", name: \"isni\" },\n { host: \"d-nb.info\", name: \"gnd\" },\n { host: \"id.loc.gov\", name: \"loc\" },\n { host: \"datos.bne.es\", name: \"bne\" },\n { host: \"wikipedia.org\", name: \"wikipedia\" },\n { host: \"musicbrainz.org\", name: \"musicbrainz\" },\n { host: \"francearchives.gouv.fr\", name: \"francearchives\" },\n { host: \"imslp.org\", name: \"imslp\" },\n { host: \"catalogue.bnf.fr\", name: \"catalogue\" },\n];\n\nconst text = (term: SparqlTerm | undefined): string | null => {\n if (!term) {\n return null;\n }\n const value = term.value.trim();\n return value === \"\" ? null : value;\n};\n\nconst integer = (term: SparqlTerm | undefined): number | null => {\n const raw = text(term);\n if (raw === null) {\n return null;\n }\n const value = Number(raw);\n return Number.isInteger(value) ? value : null;\n};\n\n/** The identifier a caller quotes, read back off the address of a record. */\nexport function idFromIri(iri: string): string | null {\n const ark = ARK_IN_IRI.exec(iri);\n if (ark?.[1]) {\n return ark[1].toLowerCase();\n }\n const temp = TEMP_WORK_IN_IRI.exec(iri);\n if (temp?.[1]) {\n return `temp-work/${temp[1].toLowerCase()}`;\n }\n return null;\n}\n\n/** The last segment of a vocabulary address, which is the term itself. */\nconst vocabularyTerm = (iri: string): string =>\n decodeURIComponent(iri.replace(TRAILING_SEPARATOR, \"\").split(PATH_SEPARATOR).pop() ?? iri);\n\n/**\n * A Gallica address turned into a link.\n *\n * Returns null for anything that is not a Gallica address, so a record pointing\n * somewhere else does not arrive labelled as a digitised BnF document. The\n * address is carried through as published: it is what a person will open.\n */\nexport function toDigitisedLink(\n address: string | null,\n role: DigitisedLink[\"role\"],\n fromId: string,\n fromTitle: string | null,\n): DigitisedLink | null {\n if (!(address && isGallicaAddress(address))) {\n return null;\n }\n const segment = ARK_SEGMENT.exec(address)?.[1] ?? null;\n if (!segment) {\n return null;\n }\n // An address can carry a rendering after the name: `.thumbnail` asks for a\n // small image of the document, `.item` for one leaf of it. An ARK name holds\n // no full stop, so what precedes the first one is the identifier and the rest\n // describes a view of it. Reporting the view as the identifier would give two\n // views of one document two identifiers.\n const ark = segment.split(\".\")[0] ?? segment;\n if (ark === \"\") {\n return null;\n }\n return { ark, url: address, rendering: renderingOf(address, ark), role, fromId, fromTitle };\n}\n\n/**\n * The view an address asks for, read off what follows the ARK name in it.\n *\n * `bpt6k90000030` names a document; `bpt6k90000030.thumbnail` asks for a small\n * image of it and `btv1b90000002/f3.item.thumbnail` for one leaf of it as an\n * image. Carrying that as a field of its own is what keeps `url` and `ark` from\n * reading as two names for one thing: a caller told only the ARK would quote a\n * document while having been handed a picture of a page of it.\n */\nfunction renderingOf(address: string, ark: string): string | null {\n const path = address.split(QUERY_START)[0] ?? address;\n const after = path.slice(path.indexOf(`ark:/12148/${ark}`) + `ark:/12148/${ark}`.length);\n const rendering = after.replace(LEADING_DOTS, \"\").replace(TRAILING_SLASHES, \"\");\n return rendering === \"\" ? null : rendering;\n}\n\n/**\n * A page, with the rows beyond it read as more existing.\n *\n * `hasMore` is true when the endpoint sent the extra row that was asked for, and\n * also when a row had to be dropped: a page that came back short because this\n * client could not read part of it is not a page with room to spare.\n */\nfunction page<T>(rows: T[], limit: number, skipped: number): Page<T> {\n const more = rows.length > limit;\n return skipped > 0\n ? { rows: rows.slice(0, limit), hasMore: more, skipped }\n : { rows: rows.slice(0, limit), hasMore: more };\n}\n\n/**\n * Whether the index window a text search reads came back full.\n *\n * The endpoint sends the occupancy on a row of its own, binding the count and\n * naming no entity. A count that reaches the size of the window means the\n * reading stopped where the window did rather than where the matches did.\n *\n * Null when no such row arrived, since a window nobody measured supports no\n * statement about what was left out.\n */\nfunction readWindowOccupancy(results: SparqlResults): boolean | null {\n for (const row of results.results.bindings) {\n const raw = text(row.windowRows);\n if (raw === null) {\n continue;\n }\n const count = Number(raw);\n if (Number.isInteger(count)) {\n return count >= TEXT_WINDOW;\n }\n }\n return null;\n}\n\n/**\n * True for the row carrying the occupancy and nothing else.\n *\n * It names no record, which is the shape of a row that had to be dropped. It is\n * neither: counting it as dropped would report the page as short of a record\n * the endpoint never claimed to send.\n */\nconst isOccupancyRow = (row: SparqlRow, entity: string): boolean =>\n row.windowRows !== undefined && row[entity] === undefined;\n\n/** Index a `?p ?o` result set by predicate, keeping every value. */\nexport function byPredicate(results: SparqlResults): Map<string, SparqlRow[]> {\n const index = new Map<string, SparqlRow[]>();\n for (const row of results.results.bindings) {\n const predicate = text(row.p);\n if (predicate === null) {\n continue;\n }\n const bucket = index.get(predicate);\n if (bucket) {\n bucket.push(row);\n } else {\n index.set(predicate, [row]);\n }\n }\n return index;\n}\n\nconst firstValue = (index: Map<string, SparqlRow[]>, predicate: string): string | null =>\n text(index.get(predicate)?.[0]?.o);\n\nconst allValues = (index: Map<string, SparqlRow[]>, predicate: string): string[] => {\n const seen = new Set<string>();\n for (const row of index.get(predicate) ?? []) {\n const value = text(row.o);\n if (value !== null) {\n seen.add(value);\n }\n }\n return [...seen];\n};\n\n/** Values that are addresses of vocabulary terms, reduced to the term. */\nconst allTerms = (index: Map<string, SparqlRow[]>, predicate: string): string[] =>\n allValues(index, predicate)\n .filter((value) => value.startsWith(\"http\"))\n .map(vocabularyTerm);\n\n/**\n * Dewey classes, and nothing else that happens to share the predicate.\n *\n * `dcterms:subject` on a work points at a Dewey class and at subject-heading\n * ARKs alike. Reducing an ARK to its last segment yields the string `about`,\n * which would then be published as a Dewey class and printed beside a real one.\n */\nconst deweyClasses = (index: Map<string, SparqlRow[]>, predicate: string): string[] =>\n allValues(index, predicate)\n .filter((value) => value.includes(\"dewey.info/class/\"))\n .map(vocabularyTerm);\n\n/** Values that are plain words rather than addresses. */\nconst allWords = (index: Map<string, SparqlRow[]>, predicate: string): string[] =>\n allValues(index, predicate).filter((value) => !value.startsWith(\"http\"));\n\n/**\n * Alignments to the same thing in other files, grouped by file.\n *\n * `owl:sameAs`, `skos:exactMatch` and `skos:closeMatch` are gathered together\n * because a caller wanting the VIAF address does not care which of the three\n * carried it, and the dataset uses all three for the same alignments. A\n * `closeMatch` is a weaker claim than the other two, and the record makes that\n * distinction nowhere a caller could act on it, so the grouping does not\n * pretend otherwise: the field is named for the addresses it holds.\n */\nfunction alignments(\n index: Map<string, SparqlRow[]>,\n selfId: string | null,\n): Record<string, string[]> {\n const found: Record<string, Set<string>> = {};\n for (const predicate of [P.sameAs, P.exactMatch, P.closeMatch, P.seeAlso]) {\n for (const address of allValues(index, predicate)) {\n if (!address.startsWith(\"http\")) {\n continue;\n }\n // An address pointing back at this record says nothing about anywhere\n // else, and the dataset carries several of those per record.\n if (selfId && address.includes(selfId)) {\n continue;\n }\n let host: string;\n try {\n host = new URL(address).hostname.toLowerCase();\n } catch {\n continue;\n }\n const known = ALIGNMENT_HOSTS.find(\n (entry) => host === entry.host || host.endsWith(`.${entry.host}`),\n );\n if (!known) {\n continue;\n }\n const addresses = found[known.name] ?? new Set<string>();\n found[known.name] = addresses;\n addresses.add(address);\n }\n }\n return Object.fromEntries(Object.entries(found).map(([name, set]) => [name, [...set]]));\n}\n\n/** ISNI is written as a bare number rather than an address. */\nfunction isniAddresses(index: Map<string, SparqlRow[]>): string[] {\n return allValues(index, P.isni).map(\n (value) => `https://isni.org/isni/${value.replace(/\\s/g, \"\")}`,\n );\n}\n\n/** Rows of an author search, with the extra row read as \"more exist\". */\nexport function toAuthorSummaries(results: SparqlResults, limit: number): Page<AuthorSummary> {\n const rows: AuthorSummary[] = [];\n const seen = new Set<string>();\n let skipped = 0;\n\n for (const row of results.results.bindings) {\n if (isOccupancyRow(row, \"person\")) {\n continue;\n }\n const iri = text(row.person);\n const id = iri === null ? null : idFromIri(iri);\n if (iri === null || id === null) {\n // A row naming no record, or naming one at an address this client cannot\n // read, is a row that was dropped. Counting it is what keeps a short page\n // from reading as a small catalogue.\n skipped += 1;\n continue;\n }\n if (seen.has(id)) {\n continue;\n }\n seen.add(id);\n rows.push({\n id,\n name: text(row.name),\n label: text(row.label),\n birthYear: integer(row.birthYear),\n deathYear: integer(row.deathYear),\n role: text(row.role),\n sourceUrl: publicPageFor(iri),\n });\n }\n\n return { ...page(rows, limit, skipped), indexWindowFull: readWindowOccupancy(results) };\n}\n\n/** One person, assembled from both halves of their record. */\nexport function toAuthorDetail(results: SparqlResults, id: string, pageUrl: string): AuthorDetail {\n const index = byPredicate(results);\n const types = allValues(index, P.type);\n\n const otherNames: Array<{ label: string; language: string | null }> = [];\n const seenLabels = new Set<string>();\n for (const row of index.get(P.altLabel) ?? []) {\n const label = text(row.o);\n if (label === null || seenLabels.has(label)) {\n continue;\n }\n seenLabels.add(label);\n otherNames.push({ label, language: text(row.lang) });\n }\n\n const sameAs = alignments(index, id);\n const isni = isniAddresses(index);\n if (isni.length > 0) {\n sameAs.isni = [...new Set([...(sameAs.isni ?? []), ...isni])];\n }\n\n const catalogueUrl =\n allValues(index, P.seeAlso).find((address) => address.includes(\"catalogue.bnf.fr\")) ?? null;\n\n const depictions: DigitisedLink[] = [];\n for (const address of allValues(index, P.depiction)) {\n const link = toDigitisedLink(address, \"depiction\", id, null);\n if (link) {\n depictions.push(link);\n }\n }\n\n return {\n id,\n name: firstValue(index, P.name),\n label: firstValue(index, P.prefLabel),\n givenName: firstValue(index, P.givenName),\n familyName: firstValue(index, P.familyName),\n otherNames,\n birthDate: firstValue(index, P.birth),\n deathDate: firstValue(index, P.death),\n birthYear: integer(index.get(P.firstYear)?.[0]?.o),\n deathYear: integer(index.get(P.lastYear)?.[0]?.o),\n birthPlace: firstValue(index, P.placeOfBirth),\n deathPlace: firstValue(index, P.placeOfDeath),\n biographicalInformation: firstValue(index, P.biographicalInformation),\n occupation: firstValue(index, P.occupation),\n languages: allTerms(index, P.languageOfThePerson),\n countries: allTerms(index, P.countryAssociated),\n fields: allWords(index, P.fieldOfActivity),\n deweyClasses: deweyClasses(index, P.fieldOfActivity),\n sameAs,\n catalogueUrl,\n recordCreated: firstValue(index, P.created),\n recordModified: firstValue(index, P.modified),\n depictions,\n types,\n truncated: results.results.bindings.length >= MOST_TRIPLES,\n sourceUrl: pageUrl,\n };\n}\n\n/**\n * How settled a record is.\n *\n * The record states it in words, and the address states it in its shape: a\n * provisional work is addressed under `temp-work` rather than by an ARK. The\n * statement governs when there is one, since a cataloguer can settle a record\n * before it is re-addressed.\n */\nexport function readStatus(statement: string | null, iri: string): \"established\" | \"provisional\" {\n if (statement !== null) {\n const said = statement.toLowerCase();\n if (said.includes(\"provisional\")) {\n return \"provisional\";\n }\n if (said.includes(\"established\")) {\n return \"established\";\n }\n }\n return iri.includes(\"/temp-work/\") ? \"provisional\" : \"established\";\n}\n\n/** Rows of a work search, gathering the creators of each work. */\nexport function toWorkSummaries(results: SparqlResults, limit: number): Page<WorkSummary> {\n const order: string[] = [];\n const byId = new Map<string, WorkSummary>();\n\n let skipped = 0;\n for (const row of results.results.bindings) {\n if (isOccupancyRow(row, \"work\")) {\n continue;\n }\n const iri = text(row.work);\n const id = iri === null ? null : idFromIri(iri);\n if (iri === null || id === null) {\n // A row naming no record, or naming one at an address this client cannot\n // read, is a row that was dropped. Counting it is what keeps a short page\n // from reading as a small catalogue.\n skipped += 1;\n continue;\n }\n\n let work = byId.get(id);\n if (!work) {\n work = {\n id,\n title: text(row.title),\n date: text(row.date),\n creators: [],\n status: readStatus(text(row.status), iri),\n sourceUrl: publicPageFor(iri),\n };\n byId.set(id, work);\n order.push(id);\n }\n\n const creatorIri = text(row.creator);\n const creatorId = creatorIri ? idFromIri(creatorIri) : null;\n if (creatorId && !work.creators.some((c) => c.id === creatorId)) {\n work.creators.push({ id: creatorId, name: text(row.creatorName) });\n }\n }\n\n return {\n ...page(\n order.map((id) => byId.get(id)).filter((one) => one !== undefined),\n limit,\n skipped,\n ),\n indexWindowFull: readWindowOccupancy(results),\n };\n}\n\n/**\n * The works one person is credited with, gathered from rows that repeat.\n *\n * A work pointing at two form terms arrives as two rows differing in one\n * column. Counting rows would report two works where the catalogue links one,\n * so the address of the work is what a work is counted by.\n */\nexport function toAuthoredWorks(results: SparqlResults, limit: number): Page<AuthoredWork> {\n const order: string[] = [];\n const byId = new Map<string, AuthoredWork>();\n\n let skipped = 0;\n for (const row of results.results.bindings) {\n const iri = text(row.work);\n const id = iri === null ? null : idFromIri(iri);\n if (iri === null || id === null) {\n // A row naming no record, or naming one at an address this client cannot\n // read, is a row that was dropped. Counting it is what keeps a short page\n // from reading as a small catalogue.\n skipped += 1;\n continue;\n }\n\n let work = byId.get(id);\n if (!work) {\n work = {\n id,\n title: text(row.title),\n date: text(row.date),\n year: integer(row.year),\n forms: [],\n status: readStatus(text(row.status), iri),\n sourceUrl: publicPageFor(iri),\n };\n byId.set(id, work);\n order.push(id);\n }\n\n const form = text(row.form);\n if (form?.startsWith(\"http\")) {\n const term = vocabularyTerm(form);\n if (!work.forms.includes(term)) {\n work.forms.push(term);\n }\n }\n }\n\n return page(\n order.map((id) => byId.get(id)).filter((one) => one !== undefined),\n limit,\n skipped,\n );\n}\n\n/** One work, assembled from its record. */\nexport function toWorkDetail(\n results: SparqlResults,\n id: string,\n iri: string,\n pageUrl: string,\n): WorkDetail {\n const index = byPredicate(results);\n const types = allValues(index, P.type);\n const truncated = results.results.bindings.length >= MOST_TRIPLES;\n\n const creators: Array<{ id: string; name: string | null }> = [];\n for (const row of index.get(P.creator) ?? []) {\n const creatorIri = text(row.o);\n const creatorId = creatorIri ? idFromIri(creatorIri) : null;\n if (!creatorId || creators.some((c) => c.id === creatorId)) {\n continue;\n }\n creators.push({ id: creatorId, name: text(row.name) });\n }\n\n const depictions: DigitisedLink[] = [];\n for (const address of allValues(index, P.depiction)) {\n const link = toDigitisedLink(address, \"depiction\", id, null);\n if (link) {\n depictions.push(link);\n }\n }\n\n const statusStatement = firstValue(index, P.statusOfIdentification);\n const catalogueUrl =\n allValues(index, P.seeAlso).find((address) => address.includes(\"catalogue.bnf.fr\")) ?? null;\n\n return {\n id,\n title: firstValue(index, P.title),\n label: firstValue(index, P.label),\n date: firstValue(index, P.date),\n firstYear: integer(index.get(P.firstYear)?.[0]?.o),\n creators,\n languages: allTerms(index, P.language),\n forms: allTerms(index, P.formOfWork),\n subjects: allWords(index, P.subjectLabel),\n deweyClasses: deweyClasses(index, P.subject),\n status: readStatus(statusStatement, iri),\n statusStatement,\n // A cut record makes this the ceiling rather than the count, so it is\n // withheld rather than published as a number the record does not support.\n expressionCount: truncated ? null : allValues(index, P.expressionOfWork).length,\n sameAs: alignments(index, id),\n catalogueUrl,\n depictions,\n types,\n truncated,\n sourceUrl: pageUrl,\n };\n}\n\n/**\n * Editions, gathered from rows that repeat.\n *\n * One manifestation carrying two digitised copies arrives as two rows differing\n * in one column. Counting rows would report two editions where the record\n * describes one, so the address of the manifestation is what an edition is\n * counted by.\n */\nexport function toEditions(results: SparqlResults, limit: number): Page<Edition> {\n const order: string[] = [];\n const byId = new Map<string, Edition>();\n\n let skipped = 0;\n for (const row of results.results.bindings) {\n const iri = text(row.edition);\n const id = iri === null ? null : idFromIri(iri);\n if (iri === null || id === null) {\n // A row naming no record, or naming one at an address this client cannot\n // read, is a row that was dropped. Counting it is what keeps a short page\n // from reading as a small catalogue.\n skipped += 1;\n continue;\n }\n\n let edition = byId.get(id);\n if (!edition) {\n edition = {\n id,\n title: text(row.title),\n date: text(row.date),\n year: integer(row.year),\n publisher: text(row.publisher),\n place: text(row.place),\n editionStatement: text(row.editionStatement),\n extent: text(row.extent),\n isbn: text(row.isbn),\n note: text(row.note),\n catalogueUrl: text(row.catalogue),\n digitised: [],\n sourceUrl: publicPageFor(iri),\n };\n byId.set(id, edition);\n order.push(id);\n }\n\n for (const [column, role] of [\n [\"reproduction\", \"reproduction\"],\n [\"ocr\", \"ocr\"],\n ] as const) {\n const link = toDigitisedLink(text(row[column]), role, id, edition.title);\n if (link && !edition.digitised.some((existing) => existing.url === link.url)) {\n edition.digitised.push(link);\n }\n }\n }\n\n return page(\n order.map((id) => byId.get(id)).filter((one) => one !== undefined),\n limit,\n skipped,\n );\n}\n\n/**\n * Every digitised document a set of rows points at, kept in one list.\n *\n * One document can be reached by several paths, so the same address arrives\n * more than once and is kept once. That makes the length of the list a poor\n * signal of whether more exist: twenty-one rows holding seventeen addresses\n * would otherwise be reported as a page with room to spare. The endpoint was\n * asked for one row beyond the page, so the count of rows is what answers that.\n */\nexport function toDigitisedLinks(results: SparqlResults, limit: number): Page<DigitisedLink> {\n const links: DigitisedLink[] = [];\n const seen = new Set<string>();\n const rowCount = results.results.bindings.length;\n\n for (const row of results.results.bindings) {\n const fromIri = text(row.edition);\n const fromId = fromIri ? (idFromIri(fromIri) ?? fromIri) : \"\";\n const title = text(row.title);\n\n for (const [column, role] of [\n [\"reproduction\", \"reproduction\"],\n [\"ocr\", \"ocr\"],\n [\"depiction\", \"depiction\"],\n ] as const) {\n const link = toDigitisedLink(text(row[column]), role, fromId, title);\n if (!link || seen.has(link.url)) {\n continue;\n }\n seen.add(link.url);\n links.push(link);\n }\n }\n\n // `rowCount` counts what the endpoint sent, including rows whose address was\n // not a Gallica one and rows naming a document already listed. Raising the\n // limit after such a page returns the same list, so the claim is confined to\n // the case where the links themselves fill the page.\n return { rows: links.slice(0, limit), hasMore: links.length > limit || rowCount > limit };\n}\n\n/** The classes an address is typed with, reduced to their vocabulary terms. */\nexport function toTypes(results: SparqlResults): string[] {\n const types = new Set<string>();\n for (const row of results.results.bindings) {\n const iri = text(row.type);\n if (iri !== null) {\n types.add(iri);\n }\n }\n return [...types];\n}\n","/**\n * Keeps this client to one request at a time, spaced out.\n *\n * data.bnf.fr is a query service a public institution runs at its own cost, and\n * a SPARQL endpoint answers an expensive question per request rather than\n * serving a file. Serialising also means a burst of tool calls cannot turn into\n * a burst of concurrent queries on one planner.\n *\n * The spacing widens when the service pushes back and narrows again on a run of\n * quiet successes, so a slow patch does not become the permanent speed.\n */\n\nexport interface RateLimiterOptions {\n /** Spacing between requests when nothing has gone wrong. */\n intervalMs: number;\n /** The widest the spacing may become under push-back. */\n maxIntervalMs?: number;\n}\n\nexport class RateLimiter {\n private readonly baseIntervalMs: number;\n private readonly maxIntervalMs: number;\n private intervalMs: number;\n private lastStartedAt = 0;\n private queue: Promise<void> = Promise.resolve();\n private calmStreak = 0;\n\n constructor(options: RateLimiterOptions) {\n this.baseIntervalMs = options.intervalMs;\n this.maxIntervalMs = options.maxIntervalMs ?? options.intervalMs * 8;\n this.intervalMs = options.intervalMs;\n }\n\n /** The spacing in force, which callers report rather than guess. */\n get currentIntervalMs(): number {\n return this.intervalMs;\n }\n\n /**\n * Run `task` with nothing else in flight.\n *\n * This only serialises. Spacing is claimed per attempt through\n * `beforeRequest`, because a task that retries makes several requests and\n * each of them owes the service the same gap.\n */\n schedule<T>(task: () => Promise<T>): Promise<T> {\n const result = this.queue.then(task, task);\n this.queue = result.then(\n () => undefined,\n () => undefined,\n );\n return result;\n }\n\n /** Wait until enough time has passed since the previous request began. */\n async beforeRequest(): Promise<void> {\n const elapsed = Date.now() - this.lastStartedAt;\n // Capped at one whole interval, so a clock that moved backwards cannot\n // produce a wait longer than the spacing itself.\n const wait = Math.min(this.intervalMs, this.intervalMs - elapsed);\n if (wait > 0) {\n await sleep(wait);\n }\n this.lastStartedAt = Date.now();\n }\n\n /** Called when the service asks for room: double the gap, up to the ceiling. */\n pushBack(): void {\n this.calmStreak = 0;\n this.intervalMs = Math.min(this.maxIntervalMs, this.intervalMs * 2);\n }\n\n /**\n * Called on a clean answer. Recovery takes several in a row, so one lucky\n * response after a rough patch does not undo the caution that earned it.\n */\n succeeded(): void {\n if (this.intervalMs === this.baseIntervalMs) {\n return;\n }\n this.calmStreak += 1;\n if (this.calmStreak < 3) {\n return;\n }\n this.calmStreak = 0;\n this.intervalMs = Math.max(this.baseIntervalMs, Math.round(this.intervalMs / 2));\n }\n}\n\nexport function sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n","/**\n * The one place that talks to data.bnf.fr.\n *\n * It holds a single rate limiter and a single cache, so pacing applies to the\n * server as a whole rather than to whichever tool happens to be running. It\n * imports nothing from the MCP layer and is published on its own, so the same\n * code serves a plain script.\n *\n * Every read runs a query, parses it and only then stores: an answer nobody\n * could parse must not be served back for the rest of the cache's lifetime.\n *\n * Every read also carries the moment the metadata came off the endpoint. The\n * licence asks for the date of retrieval alongside the source, so that date is\n * a value the layer produces rather than something a caller is trusted to add.\n */\n\nimport type { Config, Logger } from \"../config.js\";\nimport {\n MAX_ALLOWED_INTERVAL_MS,\n MIN_ALLOWED_INTERVAL_MS,\n createLogger,\n loadConfig,\n} from \"../config.js\";\nimport { notFound } from \"../errors.js\";\nimport type {\n AuthorDetail,\n AuthorSummary,\n AuthoredWork,\n DigitisedLink,\n Edition,\n Page,\n WorkDetail,\n WorkSummary,\n} from \"../types.js\";\nimport { REPO_URL } from \"../version.js\";\nimport { Cache } from \"./cache.js\";\nimport type { SparqlResults } from \"./http.js\";\nimport { runQuery } from \"./http.js\";\nimport {\n authorQuery,\n digitisedForPersonQuery,\n digitisedForWorkQuery,\n editionsQuery,\n kindQuery,\n searchAuthorsQuery,\n searchWorksQuery,\n workQuery,\n worksByCreatorQuery,\n} from \"./queries.js\";\nimport {\n toAuthorDetail,\n toAuthorSummaries,\n toAuthoredWorks,\n toDigitisedLinks,\n toEditions,\n toTypes,\n toWorkDetail,\n toWorkSummaries,\n} from \"./parse.js\";\nimport { RateLimiter } from \"./rateLimiter.js\";\nimport type { EntityId } from \"./sparql.js\";\nimport { toSearchWords } from \"./sparql.js\";\n\n/** The class data.bnf.fr types a person with. */\nconst PERSON_CLASS = \"http://xmlns.com/foaf/0.1/Person\";\n\n/**\n * The classes data.bnf.fr types a work with.\n *\n * Two vocabularies say the same thing here, and a record carries both. Either\n * one is enough, because a record carrying one has always carried the other.\n */\nconst WORK_CLASSES = new Set([\n \"http://rdvocab.info/uri/schema/FRBRentitiesRDA/Work\",\n \"http://rdaregistry.info/Elements/c/#C10001\",\n]);\n\nexport interface ClientOptions {\n config?: Partial<Config>;\n logger?: Logger;\n fetchImpl?: typeof fetch;\n}\n\n/** Every read reports where the value came from and when it was retrieved. */\nexport interface Read<T> {\n data: T;\n cached: boolean;\n /**\n * When the metadata came off data.bnf.fr, as an ISO 8601 instant.\n *\n * A cached answer keeps the moment it was first read. Reporting the moment it\n * was served would state a date of retrieval that never happened, which is\n * exactly the claim the licence asks to be accurate.\n */\n retrievedAt: string;\n /** Rows the endpoint sent that could not be read, which paging still counts. */\n skipped?: number;\n}\n\n/**\n * What this server owes the BnF, applied to whatever it is handed.\n *\n * A configuration object assembled by a caller has not been through\n * `loadConfig`, so it can carry a missing value, a value of the wrong shape, or\n * a User-Agent that names somebody else. Every bound the environment path\n * enforces is enforced here too: the published `./client` entry point is the\n * one a program uses, and it would be a strange guarantee that held only for\n * the executable.\n *\n * Three of these bounds are about what one call can cost the service. A retry\n * budget of two hundred turns a single lookup into two hundred requests against\n * an endpoint that is already failing. A deadline of an hour holds the one\n * request slot for an hour. A cache of zero entries evicts each value as it is\n * written, which switches off the caching that the three-second floor is set\n * against.\n */\nfunction withGuarantees(config: Config): Config {\n const defaults = loadConfig({});\n\n /**\n * A setting that is absent or unreadable falls back, and one outside its\n * range is brought inside it. Refusing outright is what the environment path\n * does, because a person reads stderr; a program handed an object does not.\n */\n const bounded = (value: unknown, fallback: number, min: number, max: number): number => {\n const parsed = Number(value);\n if (!Number.isFinite(parsed)) {\n return fallback;\n }\n return Math.min(max, Math.max(min, parsed));\n };\n\n const claimed = typeof config.userAgent === \"string\" ? config.userAgent.trim() : \"\";\n const identifier = defaults.userAgent;\n\n return {\n ...config,\n // A caller may say who they are. Appending rather than replacing means the\n // BnF can always tell which software is calling, and reach someone.\n userAgent:\n claimed === \"\" || claimed.includes(REPO_URL) ? identifier : `${claimed} ${identifier}`,\n minIntervalMs: bounded(\n config.minIntervalMs,\n defaults.minIntervalMs,\n MIN_ALLOWED_INTERVAL_MS,\n MAX_ALLOWED_INTERVAL_MS,\n ),\n timeoutMs: bounded(config.timeoutMs, defaults.timeoutMs, 1000, 300_000),\n maxRetries: bounded(config.maxRetries, defaults.maxRetries, 0, 8),\n cacheTtlMs: bounded(config.cacheTtlMs, defaults.cacheTtlMs, 0, 86_400_000),\n cacheMaxEntries: bounded(config.cacheMaxEntries, defaults.cacheMaxEntries, 1, 5000),\n };\n}\n\nexport class BnfClient {\n private readonly config: Config;\n private readonly logger: Logger;\n private readonly limiter: RateLimiter;\n private readonly cache: Cache<unknown>;\n /** Questions already on the wire, so an identical one waits instead of asking. */\n private readonly inFlight = new Map<string, Promise<Read<unknown>>>();\n private readonly fetchImpl: typeof fetch | undefined;\n\n constructor(options: ClientOptions = {}) {\n const base = { ...loadConfig(), ...options.config };\n this.config = withGuarantees(base);\n this.logger = options.logger ?? createLogger(this.config.logLevel);\n this.limiter = new RateLimiter({ intervalMs: this.config.minIntervalMs });\n this.cache = new Cache(this.config.cacheTtlMs, this.config.cacheMaxEntries);\n this.fetchImpl = options.fetchImpl;\n }\n\n /** The pacing in force, which widens when the endpoint pushes back. */\n get intervalMs(): number {\n return this.limiter.currentIntervalMs;\n }\n\n /** What the BnF sees this client call itself. */\n get userAgent(): string {\n return this.config.userAgent;\n }\n\n /** The deadline one query is given, which callers report rather than guess. */\n get timeoutMs(): number {\n return this.config.timeoutMs;\n }\n\n /** The one address this client sends anything to. */\n /**\n * `cacheKey` is stated rather than taken from the query, because two reads of\n * one query can produce different values: a list cut to five rows is not the\n * same value as the same list cut to twenty, and serving one for the other\n * would answer a size the caller never asked for.\n */\n private read<T>(\n cacheKey: string,\n query: string,\n parse: (results: SparqlResults) => T,\n ): Promise<Read<T>> {\n const hit = this.cache.get(cacheKey) as { value: T; retrievedAt: number } | undefined;\n if (hit !== undefined) {\n this.logger.debug(`cache hit ${cacheKey}`);\n return Promise.resolve({\n data: hit.value,\n cached: true,\n retrievedAt: new Date(hit.retrievedAt).toISOString(),\n });\n }\n\n // Two callers asking one question at the same moment both miss the cache,\n // because the first has not answered yet. Sharing the flight means the BnF\n // is asked once, which is the point of the cache in the first place.\n const flying = this.inFlight.get(cacheKey);\n if (flying !== undefined) {\n this.logger.debug(`joining a request already in flight for ${cacheKey}`);\n return flying as Promise<Read<T>>;\n }\n\n const flight = this.fetchAndParse(cacheKey, query, parse);\n this.inFlight.set(cacheKey, flight as Promise<Read<unknown>>);\n // A failure is never remembered, so the next caller asks again rather than\n // being handed the failure of somebody else's request.\n return flight.finally(() => this.inFlight.delete(cacheKey));\n }\n\n private async fetchAndParse<T>(\n cacheKey: string,\n query: string,\n parse: (results: SparqlResults) => T,\n ): Promise<Read<T>> {\n const results = await this.limiter.schedule(() =>\n runQuery({\n query,\n userAgent: this.config.userAgent,\n timeoutMs: this.config.timeoutMs,\n maxRetries: this.config.maxRetries,\n limiter: this.limiter,\n logger: this.logger,\n ...(this.fetchImpl ? { fetchImpl: this.fetchImpl } : {}),\n }),\n );\n\n // The moment is taken once the answer is in hand, so it names when the\n // metadata was read rather than when the request left.\n const retrievedAt = Date.now();\n const data = parse(results);\n this.cache.set(cacheKey, data, retrievedAt);\n return { data, cached: false, retrievedAt: new Date(retrievedAt).toISOString() };\n }\n\n /** Read an identifier a caller wrote, refusing anything this dataset cannot address. */\n /** The words a search will actually look for, which a tool reports back. */\n searchAuthors(name: string, limit: number, offset: number): Promise<Read<Page<AuthorSummary>>> {\n const words = toSearchWords(name);\n const query = searchAuthorsQuery(words, limit, offset);\n return this.read(`authors:${words.join(\"+\")}:${limit}:${offset}`, query, (results) =>\n toAuthorSummaries(results, limit),\n );\n }\n\n async getAuthor(id: EntityId): Promise<Read<AuthorDetail>> {\n const result = await this.read(`author:${id.id}`, authorQuery(id), (results) =>\n toAuthorDetail(results, id.id, id.pageUrl),\n );\n\n // The record decides what it is, and a name is not the test: a subject\n // heading and a corporate body both carry one. Answering with a person\n // record for either would put a date of birth beside something that was\n // never born, and report its absence as a fact about a life.\n if (!result.data.types.includes(PERSON_CLASS)) {\n await this.refuse(id, \"a person\", \"get_work reads a work.\");\n }\n return result;\n }\n\n /**\n * Say what an address is, when it is not the kind of thing that was asked for.\n *\n * A record the catalogue does not describe at all and a record describing\n * something else are two different answers, and only one of them means the\n * caller should go looking elsewhere.\n */\n private async refuse(id: EntityId, wanted: string, hint: string): Promise<never> {\n // A detail query gathers the classes of everything it walks through, so the\n // list it leaves behind is longer on one tool than on another and one\n // record would be described differently depending on which refused it. What\n // the address itself answers is one list, and it is the one every refusal\n // states, whatever the caller was reading at the time.\n const known = (await this.read(`types:${id.id}`, kindQuery(id), toTypes)).data;\n\n if (known.length === 0) {\n throw notFound(`data.bnf.fr describes nothing at \"${id.id}\".`, {\n hint: \"Find the identifier with search_authors or search_works rather than writing one.\",\n url: id.pageUrl,\n });\n }\n throw notFound(\n `\"${id.id}\" is described by data.bnf.fr, and it is not ${wanted}: it is typed ${known.join(\", \")}.`,\n { hint, url: id.pageUrl },\n );\n }\n\n searchWorks(title: string, limit: number, offset: number): Promise<Read<Page<WorkSummary>>> {\n const words = toSearchWords(title);\n const query = searchWorksQuery(words, limit, offset);\n return this.read(`works:${words.join(\"+\")}:${limit}:${offset}`, query, (results) =>\n toWorkSummaries(results, limit),\n );\n }\n\n async getWork(id: EntityId): Promise<Read<WorkDetail>> {\n const result = await this.read(`work:${id.id}`, workQuery(id), (results) =>\n toWorkDetail(results, id.id, id.iri, id.pageUrl),\n );\n\n // An edition carries a title too, and its ARK names a manifestation rather\n // than a work. Accepting one here would hand back an identifier described\n // as the work's, which is the identifier a citation would then carry.\n if (!result.data.types.some((type) => WORK_CLASSES.has(type))) {\n await this.refuse(\n id,\n \"a work\",\n \"get_author reads a person, and list_editions reads the editions of a work.\",\n );\n }\n return result;\n }\n\n /**\n * The works one person is named the creator of.\n *\n * The record is not checked here. A person the catalogue credits with nothing\n * and an address that names no person both answer with no rows, and telling\n * them apart costs a second query that only an empty answer needs.\n */\n worksByAuthor(id: EntityId, limit: number, offset: number): Promise<Read<Page<AuthoredWork>>> {\n return this.read(\n `author-works:${id.id}:${limit}:${offset}`,\n worksByCreatorQuery(id, limit, offset),\n (results) => toAuthoredWorks(results, limit),\n );\n }\n\n listEditions(id: EntityId, limit: number, offset: number): Promise<Read<Page<Edition>>> {\n const query = editionsQuery(id, limit, offset);\n return this.read(`editions:${id.id}:${limit}:${offset}`, query, (results) =>\n toEditions(results, limit),\n );\n }\n\n /** Digitised documents attached to a work and to its editions. */\n digitisedForWork(id: EntityId, limit: number): Promise<Read<Page<DigitisedLink>>> {\n return this.read(\n `digitised-work:${id.id}:${limit}`,\n digitisedForWorkQuery(id, limit),\n (results) => toDigitisedLinks(results, limit),\n );\n }\n\n /** Digitised documents attached to a person and to the editions of their works. */\n digitisedForPerson(id: EntityId, limit: number): Promise<Read<Page<DigitisedLink>>> {\n return this.read(\n `digitised-person:${id.id}:${limit}`,\n digitisedForPersonQuery(id, limit),\n (results) => toDigitisedLinks(results, limit),\n );\n }\n\n /** What an address is typed as, which tells a person from a work. */\n types(id: EntityId): Promise<Read<string[]>> {\n return this.read(`types:${id.id}`, kindQuery(id), toTypes);\n }\n}\n\n/**\n * What a caller needs beside the client itself.\n *\n * Every route that reads one record takes an `EntityId` rather than a string,\n * because an identifier built from a guess sends the call to a record that does\n * not exist and the answer reads as an absence. A program importing this entry\n * point therefore has to be able to build one, so the reading of an address is\n * published alongside the client that consumes it.\n */\nexport { parseEntityId } from \"./sparql.js\";\nexport type { EntityId, EntityKind } from \"./sparql.js\";\n","/**\n * find_digitised: every digitised document the catalogue attaches to one\n * person or one work, gathered as links.\n *\n * The links point at gallica.bnf.fr, and this server never requests that host.\n * The BnF places its metadata and its digitised contents under two different\n * regimes: the catalogue described here is free to reuse with its source and\n * its date of retrieval named, while Gallica's terms make use of its contents\n * inside an artificial-intelligence project subject to a paid licence outside\n * academic research, and its server refuses automated agents outright.\n *\n * So the answer is a list of addresses and what the catalogue says about each,\n * and nothing at all about what is at the other end. An OCR link in particular\n * names a machine-read text that exists; the text stays where it is.\n */\n\nimport { z } from \"zod\";\nimport type { BnfClient } from \"../bnf/client.js\";\nimport { invalidInput, notFound } from \"../errors.js\";\nimport type { DigitisedLink } from \"../types.js\";\nimport { parseEntityId } from \"../bnf/sparql.js\";\nimport { strictInput } from \"./arguments.js\";\nimport {\n GALLICA_CAVEAT,\n digitisedLinkSchema,\n ok,\n recordKindOf,\n retrievedAtSchema,\n toToolError,\n} from \"./shared.js\";\nimport type { ToolResult } from \"./shared.js\";\nimport type { EntityId } from \"../bnf/sparql.js\";\n\n/**\n * Whether the catalogue describes a person or a work at this address.\n *\n * The path a person is followed by and the path a work is followed by reach\n * different links, so the wrong one answers with part of the record or with\n * none of it. The name is only worth reading if the catalogue is what settled\n * it, so the type is read here whatever the caller wrote.\n */\nasync function whichKindTheCatalogueDescribes(\n client: BnfClient,\n id: EntityId,\n): Promise<{ kind: \"person\" | \"work\" } | { error: ToolResult }> {\n // Only a work is addressed under temp-work, so the address settles it.\n if (id.kind === \"temp-work\") {\n return { kind: \"work\" };\n }\n\n const types = await client.types(id);\n if (types.data.length === 0) {\n return {\n error: toToolError(\n notFound(`data.bnf.fr describes nothing at \"${id.id}\".`, {\n hint: \"Find the identifier with search_authors or search_works rather than writing one.\",\n url: id.pageUrl,\n }),\n ),\n };\n }\n\n const found = recordKindOf(types.data);\n if (found === null) {\n return {\n error: toToolError(\n notFound(\n `\"${id.id}\" is described by data.bnf.fr, and it is neither a person nor a work: it is typed ${types.data.join(\", \")}.`,\n {\n hint: \"This tool follows a person or a work. For one edition, list_editions carries the digitised copies alongside the imprint.\",\n url: id.pageUrl,\n },\n ),\n ),\n };\n }\n\n return { kind: found };\n}\n\nexport const findDigitisedDescription = [\n \"Gather the digitised documents the Bibliothèque nationale de France catalogue attaches to one person or one work, and return them as links.\",\n \"Give the identifier of either; the tool asks the catalogue what kind of record it is and follows the right path. For a work it walks the editions; for a person it takes the images on the record and the digitised editions of the works they are credited with. The 'kind' returned is what the catalogue types the record as.\",\n \"Every result is a link for someone to open. This server reads the BnF catalogue and never requests gallica.bnf.fr, so it reports nothing about what is at the other end: not whether the document opens, not what it contains, not on what terms it may be reused.\",\n \"It returns links and nothing else: no publisher, no date, no ISBN. Use list_editions when which edition a copy belongs to matters.\",\n \"A 'depiction' illustrates a record and can be a page that merely mentions the subject. A 'reproduction' is an edition digitised. An 'ocr' link names a machine-read text of a document, which this server does not fetch.\",\n].join(\" \");\n\nexport const findDigitisedInput = strictInput({\n id: z\n .string()\n .min(1)\n .max(200)\n .describe(\n \"A person or work identifier, such as 'cb119219976' or 'cb11970626n'. The argument is named 'id' rather than 'work_id' or 'author_id' because it takes either.\",\n ),\n kind: z\n .enum([\"auto\", \"person\", \"work\"])\n .default(\"auto\")\n .describe(\n \"What you expect the identifier to name. The catalogue is asked either way, and a kind it contradicts is refused rather than followed. 'auto' states no expectation.\",\n ),\n limit: z.number().int().min(1).max(200).default(40),\n});\n\nexport const findDigitisedOutput = z.object({\n id: z.string(),\n kind: z.enum([\"person\", \"work\"]).describe(\"What the catalogue types this record as.\"),\n links: z.array(digitisedLinkSchema),\n links_returned_by_role: z\n .object({\n reproduction: z.number().int(),\n ocr: z.number().int(),\n depiction: z.number().int(),\n })\n .describe(\n \"How many of the links in this answer carry each role. These count the links returned here, which is neither a count of documents nor a count of what the catalogue attaches: several links can name one document, and 'has_more' says when links were left out.\",\n ),\n has_more: z.boolean(),\n retrieved_at: retrievedAtSchema,\n notes: z.array(z.string()),\n});\n\nexport type FindDigitisedArgs = z.infer<typeof findDigitisedInput>;\n\nexport async function runFindDigitised(\n client: BnfClient,\n args: FindDigitisedArgs,\n): Promise<ToolResult> {\n try {\n const id = parseEntityId(args.id);\n\n const notes: string[] = [];\n\n const settled = await whichKindTheCatalogueDescribes(client, id);\n if (\"error\" in settled) {\n return settled.error;\n }\n const kind = settled.kind;\n\n if (args.kind !== \"auto\" && args.kind !== kind) {\n return toToolError(\n invalidInput(\n `The call states that \"${id.id}\" is a ${args.kind}, and data.bnf.fr describes it as a ${kind}.`,\n `Leave 'kind' out, or pass '${kind}'. Following the ${args.kind} path over a ${kind} record answers with part of the record or with none of it, and nothing in that answer would say so.`,\n ),\n );\n }\n\n const { data, cached, retrievedAt } =\n kind === \"person\"\n ? await client.digitisedForPerson(id, args.limit)\n : await client.digitisedForWork(id, args.limit);\n\n if (cached) {\n notes.push(\"Served from this server's short-lived in-memory cache.\");\n }\n\n const links = data.rows.map((link) => ({\n ark: link.ark,\n url: link.url,\n rendering: link.rendering,\n role: link.role,\n from_id: link.fromId,\n from_title: link.fromTitle,\n }));\n\n const counts = {\n reproduction: countRole(data.rows, \"reproduction\"),\n ocr: countRole(data.rows, \"ocr\"),\n depiction: countRole(data.rows, \"depiction\"),\n };\n\n const rendered = links.filter((link) => link.rendering !== null);\n\n if (links.length > 0) {\n notes.push(GALLICA_CAVEAT);\n }\n if (counts.depiction > 0) {\n notes.push(\n `${counts.depiction === 1 ? \"One of these is an image\" : `${counts.depiction} of these are images`} the catalogue uses to illustrate the record. Such an image can be a portrait, a title page, or a newspaper page that mentions the subject in passing, and the catalogue does not say which.`,\n );\n }\n if (rendered.length > 0) {\n notes.push(\n `${rendered.length === 1 ? \"One of these addresses opens\" : `${rendered.length} of these addresses open`} a rendering of a document rather than the document: what follows the ARK name in the address, such as 'thumbnail', is the view being asked for, and 'ark' names the document that view was taken from. Read 'rendering' before quoting a link as the document itself.`,\n );\n }\n if (counts.ocr > 0) {\n notes.push(\n `${counts.ocr === 1 ? \"One of these points\" : `${counts.ocr} of these point`} at a text a machine read off a scanned document. This server names that text and does not read it.`,\n );\n }\n if (data.hasMore) {\n notes.push(\n args.limit >= 200\n ? \"The catalogue points at more documents than this tool will return in one answer, and it does not page: 200 is the ceiling. Read the editions with list_editions to reach the rest, one page at a time.\"\n : `More links exist than were returned. Raise 'limit' to see more of them, up to 200. This tool does not page, so a higher limit re-reads the links already shown rather than continuing after them.`,\n );\n notes.push(\n \"The figures under 'links_returned_by_role' count the links in this answer, so they fall short of what the catalogue attaches by however much was left out.\",\n );\n }\n if (links.length === 0) {\n notes.push(\n `The catalogue attaches no digitised document to \"${id.id}\". That is a statement about the catalogue: the BnF digitises a fraction of what it holds, and a document can be on Gallica without the record here pointing at it.`,\n );\n }\n\n const body =\n links.length === 0\n ? `The BnF catalogue attaches no digitised document to \"${id.id}\".`\n : `${links.length} link(s) for \"${id.id}\" (${kind}):\\n${links\n .map((link, index) => {\n const from = link.from_title ? ` · from: ${link.from_title}` : \"\";\n return `${index + 1}. [${link.role}] ${link.url}${from}`;\n })\n .join(\"\\n\")}`;\n\n return ok(\n {\n id: id.id,\n kind,\n links,\n links_returned_by_role: counts,\n has_more: data.hasMore,\n retrieved_at: retrievedAt,\n notes,\n },\n body,\n { retrievedAt, notes, sourceUrl: id.pageUrl },\n );\n } catch (error) {\n return toToolError(error);\n }\n}\n\nconst countRole = (links: readonly DigitisedLink[], role: DigitisedLink[\"role\"]): number =>\n links.filter((link) => link.role === role).length;\n","/**\n * How a tool's arguments are declared, and what happens to one that is not.\n *\n * An argument this server does not declare is a question it cannot answer.\n * Reading it and dropping it produces an answer computed on the defaults, which\n * a caller reads as the answer to what they asked. So an undeclared argument is\n * refused, and the refusal names it and offers the declared name when one is\n * close enough to be the one that was meant.\n *\n * A caller branches on the code a refusal opens with, and arguments are refused\n * along two paths: the code of a tool writes its own refusals, and the schema\n * refuses on its own before that code runs. Both open with the same code here,\n * so a caller reading the vocabulary finds it whichever path refused.\n */\n\nimport { z } from \"zod\";\n\n/** The code a caller branches on when the arguments cannot produce a request. */\nconst INVALID_INPUT = \"invalid_input\";\n\n/** Zod's own wording. The error code is written in front of it. */\nconst defaultMessage = z.core.locales.en().localeError;\n\n/** Declare a tool's arguments, refusing anything outside the declaration. */\nexport function strictInput<Shape extends z.ZodRawShape>(shape: Shape) {\n const declared = Object.keys(shape);\n\n const refuse: z.core.$ZodErrorMap = (issue) =>\n issue.code === \"unrecognized_keys\"\n ? unknownArgumentMessage(issue.keys, declared)\n : `[${INVALID_INPUT}] ${defaultMessage(issue)}`;\n\n for (const argument of Object.values(shape)) {\n carryTheCode(argument, refuse);\n }\n\n return z.strictObject(shape, { error: refuse });\n}\n\n/**\n * Route every refusal a declared argument can raise through one error map.\n *\n * A bound such as `min` raises its issue from the check that holds it, and the\n * issue names that check as the instance whose error map applies, so the map\n * reaches it only by being written onto the check itself. Zod offers no copy\n * that keeps a check's identity, hence the assignment. It is confined to\n * schemas built for a declaration and handed to this function, which is the\n * whole of what a tool module does with them.\n */\nfunction carryTheCode(schema: z.core.$ZodType, refuse: z.core.$ZodErrorMap): void {\n const definition = schema._zod.def as {\n error?: unknown;\n checks?: Array<{ _zod: { def: { error?: unknown } } }>;\n innerType?: z.core.$ZodType;\n };\n\n definition.error = refuse;\n for (const check of definition.checks ?? []) {\n check._zod.def.error = refuse;\n }\n\n // A default, an optional or any other wrapper answers for its own shape and\n // leaves the bounds to the type it wraps.\n if (definition.innerType) {\n carryTheCode(definition.innerType, refuse);\n }\n}\n\nfunction unknownArgumentMessage(keys: readonly string[], declared: readonly string[]): string {\n const named = keys\n .map((key) => {\n const near = nearestArgument(key, declared);\n return near ? `'${key}' (did you mean '${near}'?)` : `'${key}'`;\n })\n .join(\", \");\n\n return (\n `[${INVALID_INPUT}] Unknown ${keys.length > 1 ? \"arguments\" : \"argument\"} ${named}. ` +\n `This tool takes: ${declared.join(\", \")}.`\n );\n}\n\n/**\n * The declared name a caller most plausibly meant, when there is one.\n *\n * Three readings, ordered by how much each claims: the same name written\n * differently, a name that opens or closes the other, and a name a couple of\n * typing slips away. Anything further is left unnamed, because a suggestion\n * that misses sends a caller to an argument answering a different question.\n */\nfunction nearestArgument(key: string, declared: readonly string[]): string | undefined {\n const flatten = (name: string) => name.toLowerCase().replace(/[^a-z0-9]/g, \"\");\n const flat = flatten(key);\n if (flat.length === 0) {\n return undefined;\n }\n\n const sameName = declared.find((name) => flatten(name) === flat);\n if (sameName) {\n return sameName;\n }\n\n // Either name may be the longer one: a caller can qualify a name this tool\n // keeps plain, or shorten one it spells out.\n const overlapping = declared.find((name) => {\n const other = flatten(name);\n const [shorter, longer] = other.length < flat.length ? [other, flat] : [flat, other];\n // Two characters in common say nothing; three start to.\n return shorter.length >= 3 && (longer.startsWith(shorter) || longer.endsWith(shorter));\n });\n if (overlapping) {\n return overlapping;\n }\n\n let closest: string | undefined;\n let shortest = Number.POSITIVE_INFINITY;\n for (const name of declared) {\n const distance = editDistance(flat, flatten(name));\n if (distance < shortest) {\n shortest = distance;\n closest = name;\n }\n }\n\n // Up to a third of the name may differ. Past that the match is a guess.\n return shortest <= Math.max(1, Math.floor(flat.length / 3)) ? closest : undefined;\n}\n\n/**\n * Single-character insertions, deletions and substitutions between two words.\n *\n * One row of the matrix is kept at a time. The cell above comes from walking\n * that row, and the two other neighbours a cell needs, the one to its left and\n * the one diagonally above it, are carried forward from the step before, so\n * every value the walk uses is one it has just computed.\n */\nfunction editDistance(left: string, right: string): number {\n let previous = Array.from({ length: right.length + 1 }, (_, index) => index);\n let distance = right.length;\n\n for (let row = 1; row <= left.length; row += 1) {\n const current: number[] = [row];\n let diagonal = row - 1;\n let leftward = row;\n\n for (const [column, above] of previous.entries()) {\n if (column === 0) {\n continue;\n }\n const substitution = diagonal + (left[row - 1] === right[column - 1] ? 0 : 1);\n const cell = Math.min(substitution, above + 1, leftward + 1);\n current.push(cell);\n diagonal = above;\n leftward = cell;\n }\n\n previous = current;\n distance = leftward;\n }\n\n return distance;\n}\n","/** Schemas, error mapping and rendering shared by the tools. */\n\nimport { z } from \"zod\";\nimport { BnfError } from \"../errors.js\";\n\nconst ISO_DAY = /^\\d{4}-\\d{2}-\\d{2}$/;\nconst BRACKETED_TAIL = /\\(([^()]*)\\)\\s*$/;\nconst YEAR = /^\\d{4}$/;\n\n/**\n * The text block is what many clients render, and some render nothing else, so\n * it has to answer on its own. This ceiling is what keeps a list of editions\n * from arriving as several pages of publishers.\n */\nexport const MAX_TEXT_CHARS = 2400;\n\n/**\n * The credit every answer ends with.\n *\n * The BnF publishes these metadata under one condition, stated in one sentence:\n * they may be used freely provided their source is named and the date they were\n * retrieved is stated. Both halves are built into the answer rather than left to\n * whoever renders it, because a condition a caller has to remember is a\n * condition that will be forgotten.\n */\nexport const SOURCE_NAME = \"data.bnf.fr (Bibliothèque nationale de France)\";\n\n/** The date of retrieval, written as the calendar day it names. */\nexport function retrievalDay(retrievedAt: string): string {\n const day = retrievedAt.slice(0, 10);\n return ISO_DAY.test(day) ? day : retrievedAt;\n}\n\nexport function attribution(retrievedAt: string, sourceUrl?: string): string {\n const credit = `Source: ${SOURCE_NAME}, retrieved ${retrievalDay(retrievedAt)}`;\n return sourceUrl ? `${credit} — ${sourceUrl}` : credit;\n}\n\nexport interface ToolResult {\n // The SDK's result type carries an index signature for protocol extensions.\n [key: string]: unknown;\n content: Array<{ type: \"text\"; text: string }>;\n structuredContent?: Record<string, unknown>;\n isError?: boolean;\n}\n\n/** The moment the metadata came off data.bnf.fr, carried by every answer. */\nexport const retrievedAtSchema = z\n .string()\n .describe(\n \"When these metadata were read from data.bnf.fr, as an ISO 8601 instant. The BnF licence asks for this date to be stated wherever the metadata are shown, so repeat it alongside the source.\",\n );\n\n/** A link to a digitised document, which this server describes and never opens. */\nexport const digitisedLinkSchema = z.object({\n ark: z.string().describe(\"The Gallica ARK identifying the document.\"),\n url: z\n .string()\n .describe(\n \"The address the catalogue publishes, for a person to open. Read it beside 'rendering': an address asking for a rendering opens that view of the document rather than the document. This server does not read either.\",\n ),\n rendering: z\n .string()\n .nullable()\n .describe(\n \"The view the address asks Gallica for, read off what follows the ARK name in it, such as 'thumbnail' for a small image or 'item' for one leaf. Null when the address names the document itself. This says what was asked for, not what comes back.\",\n ),\n role: z\n .enum([\"reproduction\", \"ocr\", \"depiction\"])\n .describe(\n \"'reproduction' is the edition itself digitised. 'ocr' is the text a machine read off it. 'depiction' is an image illustrating the record, which can be a portrait or a page mentioning the subject rather than the work.\",\n ),\n from_id: z.string().describe(\"The record this link hangs off.\"),\n from_title: z.string().nullable(),\n});\n\n/**\n * Keep text from data.bnf.fr out of the shape this server's own lines take.\n *\n * This server writes three kinds of line a reader treats as its own voice:\n * `Note:`, `Source:` and `Hint:`. A cataloguer's note or a title carrying those\n * same words at the start of a line is indistinguishable from one of them, and a\n * forged `Source:` line placed above the real one substitutes a different\n * attribution for the BnF's.\n *\n * Indenting such a line keeps the two apart and costs nothing: the structured\n * output still carries the text exactly as it was published. The match ignores\n * case and allows a space before the colon, because a forgery chooses its own\n * spelling, and `m` treats a carriage return, a line feed and the Unicode line\n * and paragraph separators alike.\n */\nexport function indentMarkerLines(body: string): string {\n return body.replace(/^(Note|Source|Hint)(\\s*:)/gim, \" $1$2\");\n}\n\n/**\n * Third-party text folded onto one line, for the places that build a sentence\n * around it.\n *\n * A note and an error message are each one line by construction, and the marker\n * they open with is written by this file. A value carrying a line break would\n * put whatever follows it at the start of a line of its own, where it could\n * spell `Source:` and be read as this server's attribution. Removing the break\n * is what keeps the line count equal to one, which is what makes the marker at\n * its head the only one there is.\n */\nexport function onOneLine(value: string): string {\n // U+2028 and U+2029 are written as escapes because they terminate a line in\n // source as well as in text: written literally they would end this one.\n return value.replace(/[\\r\\n\\u2028\\u2029]+/g, \" \").trim();\n}\n\n/**\n * Build a result whose text block ends with its notes and its credit.\n *\n * The body is cut to fit around the trailer rather than the whole block being\n * cut afterwards. Appending the credit and then truncating loses exactly the\n * credit and the date beside it, which are the two lines the licence requires.\n *\n * Notes qualify an answer: that a list was cut, that a search does not rank,\n * that a record is provisional. A client that shows only the text would\n * otherwise present an unqualified answer, so they travel with the credit.\n */\nexport function ok(\n structured: Record<string, unknown>,\n body: string,\n options: { retrievedAt: string; notes?: string[]; sourceUrl?: string },\n): ToolResult {\n const credit = attribution(options.retrievedAt, options.sourceUrl);\n\n // Notes are guarded the same way the body is. Several of them are built\n // around a value the BnF published, and the trailer is appended after the\n // body has been made safe, so a note is the one way third-party text could\n // otherwise reach the block already looking like a line this server wrote.\n const noteLines = (options.notes ?? []).map((note) => `Note: ${onOneLine(note)}`);\n while (noteLines.length > 0 && noteLines.join(\"\\n\").length > MAX_TEXT_CHARS / 2) {\n noteLines.pop();\n }\n const trailer = [...noteLines, credit].join(\"\\n\");\n\n const cut = \"\\n\\n[shortened; the full result is in the structured output]\";\n const budget = MAX_TEXT_CHARS - `\\n\\n${trailer}`.length;\n const safe = indentMarkerLines(body);\n const text =\n safe.length <= budget\n ? `${safe}\\n\\n${trailer}`\n : `${truncate(safe, Math.max(0, budget - cut.length))}${cut}\\n\\n${trailer}`;\n\n return { content: [{ type: \"text\", text }], structuredContent: structured };\n}\n\n/**\n * Errors carry no structured payload: the SDK checks it against the tool's\n * declared output schema, and a failure does not fit that shape.\n */\nexport function toToolError(error: unknown): ToolResult {\n const known =\n error instanceof BnfError\n ? error\n : new BnfError(\"network_error\", error instanceof Error ? error.message : String(error));\n\n // A failure message can carry text from the endpoint and identifiers a caller\n // wrote, so it is guarded like any other body.\n const lines = [`[${known.code}] ${onOneLine(known.message)}`];\n if (known.details.hint) {\n lines.push(`Hint: ${onOneLine(known.details.hint)}`);\n }\n return { content: [{ type: \"text\", text: lines.join(\"\\n\") }], isError: true };\n}\n\nexport function truncate(text: string, maxChars: number): string {\n if (text.length <= maxChars) {\n return text;\n }\n return `${text.slice(0, Math.max(0, maxChars - 1)).trimEnd()}…`;\n}\n\n/**\n * What a caller is owed when the answer rests on a window that filled up.\n *\n * A text search reads a fixed window of the index and filters what came back,\n * and the window is read first. A page can therefore hold every row the filter\n * kept while the index holds records the reading never reached. Saying the\n * window filled is a statement about the reading; no number of matches appears,\n * because the index scores nothing and a count printed beside these rows would\n * be read as a measure of them.\n *\n * The second sentence is the one a caller needs most: the window is filled in\n * the order the index answers in, which is fixed by nothing, so two readings of\n * one search can hold different records. A window with room to spare holds\n * every match, which is what makes such an answer repeatable.\n */\nexport const WINDOW_FULL_CAVEAT =\n \"The window this search reads off the index came back full, so records carrying these words sit past what was read and this list is a part of the matches. Which records fill a full window is settled by the index as it answers, so another reading of the same search can bring back other rows. Requiring one more word narrows the reading and reaches what a full window leaves out.\";\n\n/** What a full window makes of the field that says where the rows stop. */\nexport const WINDOW_FULL_AND_NO_MORE =\n \"'has_more' is false because the rows of this window stop here, and the window came back full: it says where the reading stopped rather than that the catalogue holds nothing further under these words.\";\n\n/** Wording used wherever a work-form code reaches a caller. */\nexport const FORMS_CAVEAT =\n \"The codes under 'forms' are the terms of the BnF's work-form vocabulary a record points at, passed on as they stand: the vocabulary publishes no label for them here, so some read as words, such as 'roman' or 'poesi', and others, such as 'te', say nothing on their own. A work carrying no code is a work whose form the catalogue does not state.\";\n\n/** What the codes cannot do, said where a caller holds enough rows to try. */\nexport const FORMS_FILTER_CAVEAT =\n \"A form is stated on some works and left unsaid on others, so keeping the rows that carry one code finds the works declaring it and never all the works of that form.\";\n\n/**\n * What a caller is owed wherever one authority record is taken for a person.\n *\n * The BnF opens a heading for a name it met on a document and keeps it beside\n * the fuller record for the same person, so a record can carry a name and\n * nothing else. Every link and every field hangs off a record, which makes a\n * silence on one of them a fact about that record alone.\n */\nexport const SEVERAL_RECORDS_CAVEAT =\n \"The BnF keeps more than one authority record for some people, and a record can carry a name and nothing else while a second one carries the dates, the occupation and the works. Everything here is read off this record, so search_authors under the name will show the other records the file holds and let you compare them.\";\n\n/**\n * What a caller is owed about the reading that turned their text into terms.\n *\n * Every term is mandatory, so the reading decides what the answer can be. Three\n * things about it are invisible from the answer alone: a character that marks\n * nothing having been removed, punctuation having reached no term, and a term\n * of one character being required like any other. Each of them can be what\n * emptied a list, and none of them shows in the rows.\n */\nexport function readingNotes(\n reading: { terms: string[]; invisible: string[]; setAside: string[] },\n field: \"name\" | \"title\",\n): string[] {\n const notes: string[] = [];\n const quoted = (values: readonly string[]): string =>\n values.map((value) => `\"${value}\"`).join(\", \");\n\n if (reading.invisible.length > 0) {\n notes.push(\n `The text held ${reading.invisible.length} character(s) that mark nothing on a screen, such as a control character or a zero-width space. They were removed before the words were cut, so a word one of them sat inside was searched for whole rather than as two terms the index would each require.`,\n );\n }\n if (reading.setAside.length > 0) {\n notes.push(\n `Only letters and digits reach the index: ${quoted(reading.setAside)} in the text ${reading.setAside.length === 1 ? \"was\" : \"were\"} set aside, and what was searched for is ${quoted(reading.terms)}.`,\n );\n }\n\n const single = reading.terms.filter((term) => [...term].length === 1);\n if (single.length > 0) {\n notes.push(\n `${quoted(single)} ${single.length === 1 ? \"is a term of one character\" : \"are terms of one character\"} and ${single.length === 1 ? \"is\" : \"are\"} required like any other: a record has to carry ${single.length === 1 ? \"it\" : \"them\"} in its ${field} for a row to be a match. Leaving ${single.length === 1 ? \"it\" : \"them\"} out widens the search.`,\n );\n }\n return notes;\n}\n\n/** Wording used wherever a search that does not rank reaches a caller. */\nexport const NO_RANKING =\n \"The full-text index answers whether a record matches, and it does not score how well. Rows are ordered by the address of the record, which measures nothing, so the row a person would call the obvious answer can sit anywhere in the list, or on a later page. That order is the one the pages are cut along, so reading page after page reaches every match once.\";\n\n/**\n * What a page holding no row turned out to be.\n *\n * 'absent' is the words matching no record at all. 'past_the_end' is a search\n * that matches, whose rows stop before the page asked for. 'undetermined' is\n * the reading that would tell them apart having failed.\n */\nexport type EmptyPage = \"absent\" | \"past_the_end\" | \"undetermined\";\n\n/**\n * Tell a page asked for beyond the last row from a search that matches nothing.\n *\n * Both come back as a page of no rows, and calling either one an absence states\n * something the reading did not establish. Since a search reports no total, the\n * only way to separate them is to read the search from its first row: a row\n * there means the words match and the page asked for sits past where the rows\n * stop. The first page is its own evidence and is never read twice.\n */\nexport async function classifyEmptyPage(\n page: number,\n firstPageHasRow: () => Promise<boolean>,\n): Promise<EmptyPage> {\n if (page === 1) {\n return \"absent\";\n }\n try {\n return (await firstPageHasRow()) ? \"past_the_end\" : \"absent\";\n } catch {\n return \"undetermined\";\n }\n}\n\n/** The class data.bnf.fr types a person with. */\nexport const PERSON_TYPE = \"http://xmlns.com/foaf/0.1/Person\";\n\n/** The classes data.bnf.fr types a work with, one per vocabulary it uses. */\nexport const WORK_TYPES: readonly string[] = [\n \"http://rdvocab.info/uri/schema/FRBRentitiesRDA/Work\",\n \"http://rdaregistry.info/Elements/c/#C10001\",\n];\n\n/**\n * What the catalogue says an address is.\n *\n * An edition, an expression and a subject heading carry identifiers of the same\n * shape as a person and a work, and a tool that reads one of them as the other\n * asks a question it can never answer. The empty answer that comes back is\n * indistinguishable from the BnF holding nothing, so the type is read and null\n * is returned for everything else, which the caller refuses by name.\n */\nexport function recordKindOf(types: readonly string[]): \"person\" | \"work\" | null {\n if (types.includes(PERSON_TYPE)) {\n return \"person\";\n }\n if (types.some((type) => WORK_TYPES.includes(type))) {\n return \"work\";\n }\n return null;\n}\n\n/** The years a BnF heading states in its brackets, read off the end of it. */\nfunction headingYears(label: string): { birth: number | null; death: number | null } {\n const bracketed = BRACKETED_TAIL.exec(label);\n const [birth, death] = (bracketed?.[1] ?? \"\").split(\"-\");\n const year = (part: string | undefined): number | null =>\n part !== undefined && YEAR.test(part.trim()) ? Number(part.trim()) : null;\n return { birth: year(birth), death: year(death) };\n}\n\n/**\n * Where a heading and the dated fields of the same record disagree.\n *\n * A BnF heading writes the dates in brackets and the record states them again\n * as numbers, and a correction applied to one side and not the other leaves the\n * record saying two things. The numbers are what a caller compares to tell two\n * people of one name apart, so a disagreement is reported: this server has no\n * way of knowing which side a cataloguer meant, and picking one would hand over\n * a date the record does not settle.\n */\nexport function headingYearConflicts(\n label: string | null,\n birthYear: number | null,\n deathYear: number | null,\n): string[] {\n if (label === null) {\n return [];\n }\n const stated = headingYears(label);\n const conflicts: string[] = [];\n if (stated.birth !== null && birthYear !== null && stated.birth !== birthYear) {\n conflicts.push(`${stated.birth} against 'birth_year' ${birthYear}`);\n }\n if (stated.death !== null && deathYear !== null && stated.death !== deathYear) {\n conflicts.push(`${stated.death} against 'death_year' ${deathYear}`);\n }\n return conflicts;\n}\n\n/** Wording used wherever a Gallica link reaches a caller. */\nexport const GALLICA_CAVEAT =\n \"These are links for a person to open. This server reads the BnF catalogue and never requests gallica.bnf.fr, so it cannot say whether a document opens, what it contains, or on what terms it may be reused.\";\n\n/**\n * Wording used wherever the addresses under 'same_as' reach a caller.\n *\n * They are the BnF's own alignments, carried through character for character:\n * an address is a key, and re-spelling one, by decoding an escape or by adding\n * an escape of this server's own, produces an address the catalogue never\n * published which can name another page or none. This server requests\n * data.bnf.fr and nothing else, so whether any of them answers today is\n * something it has not looked at and does not claim.\n */\nexport const ALIGNMENTS_CAVEAT =\n \"The addresses under 'same_as' are the BnF's own alignments to other files, passed on exactly as the catalogue publishes them, character for character. This server requests data.bnf.fr alone and does not open them, so it cannot say whether one still answers or what stands at the other end.\";\n\n/** Wording used wherever a provisional record reaches a caller. */\nexport const PROVISIONAL_CAVEAT =\n \"A provisional record is one the BnF created to hold a title it has catalogued and not yet established as a work of its own. Its identifier can change, and it can be merged into an established record.\";\n","/**\n * get_author: read one person's authority record.\n *\n * The record is a set of statements a cataloguer entered, and its shape says\n * what it is for. Dates, places, a language, a country, a Dewey class and\n * alignments to VIAF, IdRef and DBpedia are all there and all reliable. A\n * biography is not: `biographicalInformation` holds \"Poète\" on Rimbaud, and a\n * caller asked for a life story has to be told that the field is a job title\n * rather than handed one word dressed up as one.\n */\n\nimport { z } from \"zod\";\nimport type { BnfClient } from \"../bnf/client.js\";\nimport { parseEntityId } from \"../bnf/sparql.js\";\nimport { strictInput } from \"./arguments.js\";\nimport {\n ALIGNMENTS_CAVEAT,\n GALLICA_CAVEAT,\n SEVERAL_RECORDS_CAVEAT,\n digitisedLinkSchema,\n headingYearConflicts,\n ok,\n retrievedAtSchema,\n toToolError,\n} from \"./shared.js\";\nimport type { ToolResult } from \"./shared.js\";\nimport type { AuthorDetail } from \"../types.js\";\n\n/**\n * What the catalogue's own shape says about the record, beyond its fields.\n *\n * Each sentence answers something a reader would otherwise conclude wrongly: a\n * field that holds an occupation rather than a life, one statement written\n * twice, dates that disagree with the heading they sit under, and a silence\n * about a death that means different things depending on whether the record\n * dates the person at all.\n */\nfunction notesOnWhatTheRecordStates(data: AuthorDetail, args: GetAuthorArgs): string[] {\n const notes: string[] = [];\n\n if (data.biographicalInformation !== null) {\n notes.push(\n `The BnF states \"${data.biographicalInformation}\" as this person's biographical information. That field holds an occupation rather than a life, and this is all the catalogue says.`,\n );\n }\n // Two fields carrying one string are one statement. Read as two they\n // corroborate each other, and a reader counts a second source that the\n // record does not hold.\n if (data.biographicalInformation !== null && data.biographicalInformation === data.occupation) {\n notes.push(\n \"This record states the same text under 'biographical_information' and 'occupation', character for character. It is one statement written in two fields rather than two the record makes separately.\",\n );\n }\n // A record can carry its dates twice, in the brackets of a heading and in\n // the fields, and the two can disagree. The fields are what the searches\n // read and what a caller compares to tell two people of one name apart, so\n // the disagreement is stated: nothing on the record says which side a\n // cataloguer meant, and choosing one would settle a date the BnF has not.\n const headings = [data.label, ...data.otherNames.map((other) => other.label)];\n const conflicts: string[] = [\n ...new Set(\n headings.flatMap((heading) => headingYearConflicts(heading, data.birthYear, data.deathYear)),\n ),\n ];\n if (conflicts.length > 0) {\n notes.push(\n `This record states its dates twice and the two disagree: the heading says ${conflicts.join(\", and \")}. Both are passed on as the BnF publishes them, and the record says nothing about which is right, so read source_url before citing either.`,\n );\n }\n\n // What the silence about a death is worth depends on whether the record\n // dates the person at all. A record carrying a birth places them in time\n // and leaves one statement missing; a record carrying neither places them\n // nowhere, and reading a life out of it is reading a life out of a heading.\n if (data.deathDate === null && data.deathYear === null) {\n const dated = data.birthDate !== null || data.birthYear !== null;\n notes.push(\n dated\n ? \"The record states no date of death. That can mean the person is living, or that the BnF has not recorded one; the catalogue does not distinguish the two.\"\n : `This record states no date of birth and no date of death, so it dates the person nowhere and says nothing about whether they are living. ${SEVERAL_RECORDS_CAVEAT}`,\n );\n }\n if (Object.keys(data.sameAs).length > 0) {\n notes.push(\n `${ALIGNMENTS_CAVEAT} Following one is how to reach a biography, which this catalogue does not hold.`,\n );\n }\n // The caveat is about links a caller is holding. On an answer that returns\n // none, it describes a list that is not there and reads as a list withheld.\n if (args.include_depictions && data.depictions.length > 0) {\n notes.push(GALLICA_CAVEAT);\n }\n\n return notes;\n}\n\nexport const getAuthorDescription = [\n \"Read one person's record in the Bibliothèque nationale de France authority file, by the identifier search_authors returns.\",\n \"It carries the dates and places of birth and death, the occupation the record names, the language and country it associates with the person, the field of activity with its Dewey class, and the addresses for the same person in VIAF, IdRef, DBpedia, Wikidata and ISNI.\",\n \"It carries no biography. The field the BnF calls biographical information is a job title on most records and a single word on many, so quote it as what the catalogue states and do not build a life story out of it.\",\n \"It lists no work. list_works walks from this record to the works the catalogue names the person the creator of, and states what that link does and does not reach.\",\n \"'depictions' are images data.bnf.fr attaches to the person. They are links for a person to open; this server does not read them, and an image can be a page that mentions the person rather than a portrait of them.\",\n].join(\" \");\n\nexport const getAuthorInput = strictInput({\n author_id: z\n .string()\n .min(1)\n .max(200)\n .describe(\n \"The identifier from search_authors, such as 'cb119219976'. A full data.bnf.fr address is accepted too.\",\n ),\n include_depictions: z\n .boolean()\n .default(false)\n .describe(\n \"Include the images data.bnf.fr attaches to the person. A well-known person can carry dozens, so they are left out by default. find_digitised gathers them with everything else.\",\n ),\n});\n\nexport const getAuthorOutput = z.object({\n author: z.object({\n id: z.string(),\n name: z.string().nullable(),\n label: z.string().nullable().describe(\"The authority heading, usually with the dates.\"),\n given_name: z.string().nullable(),\n family_name: z.string().nullable(),\n other_names: z\n .array(z.object({ label: z.string(), language: z.string().nullable() }))\n .describe(\"Other headings the record carries, including transliterations and pen names.\"),\n birth_date: z.string().nullable().describe(\"As published, usually YYYY-MM-DD.\"),\n death_date: z.string().nullable(),\n birth_year: z.number().int().nullable(),\n death_year: z.number().int().nullable(),\n birth_place: z.string().nullable(),\n death_place: z.string().nullable(),\n biographical_information: z\n .string()\n .nullable()\n .describe(\n \"What the record states about the person. This is a job title on most records and a single word on many; it is not a biography.\",\n ),\n occupation: z.string().nullable(),\n languages: z.array(z.string()).describe(\"ISO 639-2 codes, such as 'fre'.\"),\n countries: z.array(z.string()).describe(\"Country codes, such as 'fr'.\"),\n fields: z.array(z.string()).describe(\"Fields of activity in the words of the record.\"),\n dewey_classes: z\n .array(z.string())\n .describe(\"Dewey classes the fields point at, such as '800' for literature.\"),\n same_as: z\n .record(z.string(), z.array(z.string()))\n .describe(\"Addresses for the same person in other files, grouped by file.\"),\n catalogue_url: z\n .string()\n .nullable()\n .describe(\n \"The address of this record in the BnF general catalogue, as the record itself points at it. Null when the record states none, which is a silence in the record rather than a record the general catalogue lacks: no address is built in its place.\",\n ),\n record_created: z.string().nullable().describe(\"When the authority record was created.\"),\n record_modified: z.string().nullable().describe(\"When it was last changed.\"),\n source_url: z.string(),\n }),\n depictions: z.array(digitisedLinkSchema).optional(),\n depiction_count: z\n .number()\n .int()\n .describe(\n \"Images on Gallica that the record points at, counted whether or not they were returned. An illustration the record holds elsewhere is not counted, because this server describes Gallica documents and knows nothing about the others.\",\n ),\n retrieved_at: retrievedAtSchema,\n notes: z.array(z.string()),\n});\n\nexport type GetAuthorArgs = z.infer<typeof getAuthorInput>;\n\nexport async function runGetAuthor(client: BnfClient, args: GetAuthorArgs): Promise<ToolResult> {\n try {\n const id = parseEntityId(args.author_id);\n const { data, cached, retrievedAt } = await client.getAuthor(id);\n\n const notes: string[] = [];\n if (cached) {\n notes.push(\"Served from this server's short-lived in-memory cache.\");\n }\n\n notes.push(...notesOnWhatTheRecordStates(data, args));\n\n const structured: Record<string, unknown> = {\n author: {\n id: data.id,\n name: data.name,\n label: data.label,\n given_name: data.givenName,\n family_name: data.familyName,\n other_names: data.otherNames,\n birth_date: data.birthDate,\n death_date: data.deathDate,\n birth_year: data.birthYear,\n death_year: data.deathYear,\n birth_place: data.birthPlace,\n death_place: data.deathPlace,\n biographical_information: data.biographicalInformation,\n occupation: data.occupation,\n languages: data.languages,\n countries: data.countries,\n fields: data.fields,\n dewey_classes: data.deweyClasses,\n same_as: data.sameAs,\n catalogue_url: data.catalogueUrl,\n record_created: data.recordCreated,\n record_modified: data.recordModified,\n source_url: data.sourceUrl,\n },\n depiction_count: data.depictions.length,\n retrieved_at: retrievedAt,\n notes,\n };\n\n if (args.include_depictions) {\n structured.depictions = data.depictions.map((link) => ({\n ark: link.ark,\n url: link.url,\n rendering: link.rendering,\n role: link.role,\n from_id: link.fromId,\n from_title: link.fromTitle,\n }));\n }\n\n const born = [\n data.birthDate ?? (data.birthYear === null ? null : String(data.birthYear)),\n data.birthPlace,\n ]\n .filter(Boolean)\n .join(\", \");\n const died = [\n data.deathDate ?? (data.deathYear === null ? null : String(data.deathYear)),\n data.deathPlace,\n ]\n .filter(Boolean)\n .join(\", \");\n\n const lines = [\n data.label ?? data.name ?? data.id,\n born ? `Born: ${born}` : \"\",\n died ? `Died: ${died}` : \"\",\n data.occupation ? `Occupation as recorded: ${data.occupation}` : \"\",\n data.fields.length > 0\n ? `Field of activity: ${data.fields.join(\", \")}${data.deweyClasses.length > 0 ? ` (Dewey ${data.deweyClasses.join(\", \")})` : \"\"}`\n : \"\",\n data.languages.length > 0 ? `Language: ${data.languages.join(\", \")}` : \"\",\n data.countries.length > 0 ? `Country: ${data.countries.join(\", \")}` : \"\",\n data.otherNames.length > 0\n ? `Also recorded as: ${data.otherNames.map((other) => other.label).join(\"; \")}`\n : \"\",\n Object.keys(data.sameAs).length > 0\n ? `Same person in: ${Object.entries(data.sameAs)\n .map(([file, addresses]) => `${file} ${addresses[0]}`)\n .join(\" · \")}`\n : \"\",\n data.catalogueUrl ? `BnF catalogue: ${data.catalogueUrl}` : \"\",\n data.depictions.length > 0\n ? `Images attached to this record: ${data.depictions.length}${args.include_depictions ? \"\" : \" (ask with include_depictions, or use find_digitised)\"}`\n : \"\",\n ].filter(Boolean);\n\n return ok(structured, lines.join(\"\\n\"), { retrievedAt, notes, sourceUrl: data.sourceUrl });\n } catch (error) {\n return toToolError(error);\n }\n}\n","/**\n * get_work: read one work's record.\n *\n * The BnF holds two kinds of work record, and telling them apart is the point\n * of this tool. An established work has an ARK of its own, a settled title, and\n * links to every expression the catalogue knows. A provisional one is addressed\n * under `temp-work`, was created to hold a title a cataloguer has not yet\n * settled, and can be renamed, re-addressed or merged away. Quoting a\n * provisional identifier as a stable one is how a citation goes bad quietly, so\n * the record says which it is in words rather than leaving it to be read off\n * the shape of an address.\n */\n\nimport { z } from \"zod\";\nimport type { BnfClient } from \"../bnf/client.js\";\nimport { parseEntityId } from \"../bnf/sparql.js\";\nimport { strictInput } from \"./arguments.js\";\nimport {\n ALIGNMENTS_CAVEAT,\n FORMS_CAVEAT,\n GALLICA_CAVEAT,\n PROVISIONAL_CAVEAT,\n digitisedLinkSchema,\n ok,\n retrievedAtSchema,\n toToolError,\n} from \"./shared.js\";\nimport type { ToolResult } from \"./shared.js\";\nimport type { WorkDetail } from \"../types.js\";\n\n/**\n * What the catalogue's own shape says about the work, beyond its fields.\n *\n * A provisional record, a form stated as a code the catalogue publishes no\n * label for, an alignment that reaches a description this catalogue does not\n * hold: each is something a reader would otherwise take for more than it is.\n */\nfunction notesOnWhatTheRecordStates(data: WorkDetail, args: GetWorkArgs): string[] {\n const notes: string[] = [];\n\n if (data.status === \"provisional\") {\n notes.push(\n `This is a provisional record, addressed under 'temp-work' rather than by an ARK${data.statusStatement ? ` and stated as \"${data.statusStatement}\"` : \"\"}. ${PROVISIONAL_CAVEAT}`,\n );\n } else {\n notes.push(\n `This is an established work record, addressed by the ARK '${data.id}'${data.statusStatement ? ` and stated as \"${data.statusStatement}\"` : \"\"}. That identifier is the one to cite.`,\n );\n }\n\n if (data.truncated) {\n notes.push(\n \"This record is longer than one query reads, so parts of it are missing from this answer. A field shown as empty here may be filled on the record itself: open source_url before concluding that the BnF states nothing.\",\n );\n }\n if (data.expressionCount !== null && data.expressionCount > 0) {\n notes.push(\n `The record links ${data.expressionCount} expression(s): translations, adaptations and recordings of this work. Published editions are a separate count, and list_editions is what reads them.`,\n );\n }\n // The codes are printed in the body and in the payload, so what they are\n // travels with them: a reader shown 'poesi' beside 'te' has no way of\n // telling a word from an opaque term without being told.\n if (data.forms.length > 0) {\n notes.push(FORMS_CAVEAT);\n }\n if (data.creators.length === 0) {\n notes.push(\n \"The record credits nobody with this work. That is normal for anonymous and traditional works, and it also happens on records a cataloguer has not finished.\",\n );\n }\n if (Object.keys(data.sameAs).length > 0) {\n notes.push(ALIGNMENTS_CAVEAT);\n }\n // The caveat is about links a caller is holding. On an answer that returns\n // none, it describes a list that is not there and reads as a list withheld.\n if (args.include_depictions && data.depictions.length > 0) {\n notes.push(GALLICA_CAVEAT);\n }\n\n return notes;\n}\n\nexport const getWorkDescription = [\n \"Read one work's record in the Bibliothèque nationale de France catalogue, by the identifier search_works returns.\",\n \"It carries the title, everyone the record credits, the date the BnF gives the work, its language, its form and its subject with the Dewey class.\",\n \"'forms' holds the work-form vocabulary's own terms, which carry no label in this dataset, so some read as words and some do not. A record stating none has a form the catalogue does not state.\",\n \"'status' says whether the record is established or provisional, and 'status_statement' repeats what the catalogue itself states. A provisional identifier can change, so cite an established one where there is a choice.\",\n \"'expression_count' counts the expressions the record links, which is not a count of published editions: use list_editions for those.\",\n].join(\" \");\n\nexport const getWorkInput = strictInput({\n work_id: z\n .string()\n .min(1)\n .max(200)\n .describe(\n \"The identifier from search_works, such as 'cb11970626n' or 'temp-work/22d7f68c1a4bdd081ad7ca791fd3b730'.\",\n ),\n include_depictions: z\n .boolean()\n .default(false)\n .describe(\n \"Include the images data.bnf.fr attaches to the work. A well-known work can carry dozens, so they are left out by default. find_digitised gathers them alongside the digitised editions.\",\n ),\n});\n\nexport const getWorkOutput = z.object({\n work: z.object({\n id: z.string(),\n title: z.string().nullable(),\n label: z.string().nullable(),\n date: z.string().nullable().describe(\"The date the record gives the work, as published.\"),\n first_year: z.number().int().nullable(),\n creators: z.array(z.object({ id: z.string(), name: z.string().nullable() })),\n languages: z.array(z.string()).describe(\"ISO 639-2 codes.\"),\n forms: z\n .array(z.string())\n .describe(\n \"Terms of the BnF work-form vocabulary, as the terms themselves. The vocabulary publishes no label for them here, so they are not translated: some read as words, such as 'poesi', and some do not, such as 'te'. An empty list is a form the record does not state.\",\n ),\n subjects: z.array(z.string()).describe(\"Subjects in the words of the record.\"),\n dewey_classes: z.array(z.string()),\n status: z.enum([\"established\", \"provisional\"]),\n status_statement: z\n .string()\n .nullable()\n .describe(\"What the record itself states, such as 'fully established' or 'provisional'.\"),\n expression_count: z\n .number()\n .int()\n .nullable()\n .describe(\n \"Expressions the record links: translations, adaptations, recordings. This is not a count of published editions. Null when the record was longer than one query reads, since the number would then be the ceiling rather than the count.\",\n ),\n same_as: z.record(z.string(), z.array(z.string())),\n catalogue_url: z\n .string()\n .nullable()\n .describe(\n \"The address of this record in the BnF general catalogue, as the record itself points at it. Null when the record states none, which is the ordinary case on work records: it is a silence in the record rather than a record the general catalogue lacks, and no address is built in its place.\",\n ),\n source_url: z.string(),\n }),\n depictions: z.array(digitisedLinkSchema).optional(),\n depiction_count: z\n .number()\n .int()\n .describe(\n \"Images on Gallica that the record points at, counted whether or not they were returned.\",\n ),\n retrieved_at: retrievedAtSchema,\n notes: z.array(z.string()),\n});\n\nexport type GetWorkArgs = z.infer<typeof getWorkInput>;\n\nexport async function runGetWork(client: BnfClient, args: GetWorkArgs): Promise<ToolResult> {\n try {\n const id = parseEntityId(args.work_id);\n const { data, cached, retrievedAt } = await client.getWork(id);\n\n const notes: string[] = [];\n if (cached) {\n notes.push(\"Served from this server's short-lived in-memory cache.\");\n }\n\n notes.push(...notesOnWhatTheRecordStates(data, args));\n\n const structured: Record<string, unknown> = {\n work: {\n id: data.id,\n title: data.title,\n label: data.label,\n date: data.date,\n first_year: data.firstYear,\n creators: data.creators,\n languages: data.languages,\n forms: data.forms,\n subjects: data.subjects,\n dewey_classes: data.deweyClasses,\n status: data.status,\n status_statement: data.statusStatement,\n expression_count: data.expressionCount,\n same_as: data.sameAs,\n catalogue_url: data.catalogueUrl,\n source_url: data.sourceUrl,\n },\n depiction_count: data.depictions.length,\n retrieved_at: retrievedAt,\n notes,\n };\n\n if (args.include_depictions) {\n structured.depictions = data.depictions.map((link) => ({\n ark: link.ark,\n url: link.url,\n rendering: link.rendering,\n role: link.role,\n from_id: link.fromId,\n from_title: link.fromTitle,\n }));\n }\n\n const lines = [\n [data.title ?? data.label ?? data.id, data.date ? `(${data.date})` : \"\"]\n .filter(Boolean)\n .join(\" \"),\n data.creators.length > 0\n ? `By: ${data.creators.map((creator) => creator.name ?? creator.id).join(\", \")}`\n : \"\",\n `Record: ${data.status === \"provisional\" ? \"provisional\" : \"established\"}`,\n data.languages.length > 0 ? `Language: ${data.languages.join(\", \")}` : \"\",\n data.forms.length > 0 ? `Form: ${data.forms.join(\", \")}` : \"\",\n data.subjects.length > 0\n ? `Subject: ${data.subjects.join(\", \")}${data.deweyClasses.length > 0 ? ` (Dewey ${data.deweyClasses.join(\", \")})` : \"\"}`\n : \"\",\n data.catalogueUrl ? `BnF catalogue: ${data.catalogueUrl}` : \"\",\n ].filter(Boolean);\n\n return ok(structured, lines.join(\"\\n\"), { retrievedAt, notes, sourceUrl: data.sourceUrl });\n } catch (error) {\n return toToolError(error);\n }\n}\n","/**\n * list_editions: the published editions of one work.\n *\n * A manifestation is what a reader would call an edition: a publisher, a place,\n * a year, an extent, and sometimes a digitised copy. What the catalogue does\n * not record is whether that copy can be opened, by whom, or under what terms,\n * and there is no field that would let this server work it out. So a row that\n * carries a Gallica link says a link exists, and stops there.\n */\n\nimport { z } from \"zod\";\nimport type { BnfClient } from \"../bnf/client.js\";\nimport type { EntityId } from \"../bnf/sparql.js\";\nimport { notFound } from \"../errors.js\";\nimport { parseEntityId } from \"../bnf/sparql.js\";\nimport { strictInput } from \"./arguments.js\";\nimport {\n GALLICA_CAVEAT,\n digitisedLinkSchema,\n ok,\n recordKindOf,\n retrievedAtSchema,\n toToolError,\n} from \"./shared.js\";\nimport type { ToolResult } from \"./shared.js\";\n\n/**\n * Why a page of editions came back empty.\n *\n * A page past the last one and a work the catalogue links no edition to are\n * different statements about the catalogue, and a caller cannot act on the\n * first the way they would act on the second.\n */\nfunction nothingOnThisPage(page: number, work: string): string {\n if (page > 1) {\n return `Page ${page} of the editions of \"${work}\" holds no row.`;\n }\n return `The BnF catalogue links no edition to the work \"${work}\".`;\n}\n\nexport const listEditionsDescription = [\n \"List the published editions of one work in the Bibliothèque nationale de France catalogue, by the identifier search_works or get_work returns.\",\n \"Each row carries the publisher, the place, the year, the edition statement, the extent in the words of the record, the ISBN when there is one, and the record in the BnF general catalogue.\",\n \"An edition that has been digitised carries a link under 'digitised'. That link says a copy exists at that address; this server never opens it, so it cannot say whether the document is readable, complete, or free to reuse.\",\n \"Rows are ordered by the address the catalogue gives each edition, which is neither by date nor by importance.\",\n].join(\" \");\n\nexport const listEditionsInput = strictInput({\n work_id: z.string().min(1).max(200).describe(\"The work identifier, such as 'cb11970626n'.\"),\n limit: z.number().int().min(1).max(50).default(10),\n page: z.number().int().min(1).max(100).default(1),\n});\n\nexport const editionRowSchema = z.object({\n id: z.string().describe(\"The edition's own identifier in the BnF catalogue.\"),\n title: z.string().nullable().describe(\"The title as this edition carries it.\"),\n date: z\n .string()\n .nullable()\n .describe(\"The date as published, which can be a phrase such as '[s.d.]' for an undated one.\"),\n year: z.number().int().nullable().describe(\"The year, when the record states one as a number.\"),\n publisher: z\n .string()\n .nullable()\n .describe(\n \"The publisher as the record writes it, square brackets included. A bracketed part is the cataloguer speaking rather than the item: '[s.n.]' is a publisher the item does not name, and a role such as '[éd., distrib.]' is what the named house did rather than part of its name.\",\n ),\n place: z\n .string()\n .nullable()\n .describe(\n \"The place of publication as the record writes it, square brackets included. '[Paris]' is a place the cataloguer supplied because the item does not print one, and '[S.l.]' is an item stating no place at all.\",\n ),\n edition_statement: z.string().nullable().describe(\"Such as a numbered or revised edition.\"),\n extent: z\n .string()\n .nullable()\n .describe(\"The extent in the words of the record: pages, volumes, discs.\"),\n isbn: z.string().nullable(),\n note: z.string().nullable().describe(\"What the cataloguer wrote, as published.\"),\n catalogue_url: z.string().nullable(),\n digitised: z\n .array(digitisedLinkSchema)\n .describe(\n \"Digitised copies of this edition, as links. An empty list means the catalogue records none here, not that none exists.\",\n ),\n source_url: z.string(),\n});\n\nexport const listEditionsOutput = z.object({\n work_id: z.string(),\n editions: z.array(editionRowSchema),\n page: z.number().int(),\n has_more: z.boolean(),\n digitised_count: z.number().int().describe(\"Editions on this page carrying at least one link.\"),\n retrieved_at: retrievedAtSchema,\n notes: z.array(z.string()),\n});\n\nexport type ListEditionsArgs = z.infer<typeof listEditionsInput>;\n\nexport async function runListEditions(\n client: BnfClient,\n args: ListEditionsArgs,\n): Promise<ToolResult> {\n try {\n const id = parseEntityId(args.work_id);\n const offset = (args.page - 1) * args.limit;\n const { data, cached, retrievedAt } = await client.listEditions(id, args.limit, offset);\n\n const notes: string[] = [];\n if (cached) {\n notes.push(\"Served from this server's short-lived in-memory cache.\");\n }\n\n const editions = data.rows.map((row) => ({\n id: row.id,\n title: row.title,\n date: row.date,\n year: row.year,\n publisher: row.publisher,\n place: row.place,\n edition_statement: row.editionStatement,\n extent: row.extent,\n isbn: row.isbn,\n note: row.note,\n catalogue_url: row.catalogueUrl,\n digitised: row.digitised.map((link) => ({\n ark: link.ark,\n url: link.url,\n rendering: link.rendering,\n role: link.role,\n from_id: link.fromId,\n from_title: link.fromTitle,\n })),\n source_url: row.sourceUrl,\n }));\n\n const digitisedCount = editions.filter((edition) => edition.digitised.length > 0).length;\n if (digitisedCount > 0) {\n notes.push(GALLICA_CAVEAT);\n }\n\n // A bracketed value is a cataloguing convention, and a caller building a\n // citation out of one copies the cataloguer's aside into it as if the item\n // carried it. The values are passed on as published, so what the brackets\n // do is said rather than removed.\n const bracketed = editions.some(\n (edition) =>\n (edition.publisher?.includes(\"[\") ?? false) || (edition.place?.includes(\"[\") ?? false),\n );\n if (bracketed) {\n notes.push(\n \"Square brackets in 'place' and 'publisher' are the cataloguer's own, kept as the record writes them: '[S.l.]' and '[s.n.]' are an item naming no place and no publisher, a bracketed name is one supplied from outside the item, and a bracketed role such as '[éd., distrib.]' says what a named house did. Drop them before quoting a value as what the item prints.\",\n );\n }\n\n if (data.hasMore) {\n notes.push(`More editions exist beyond this page. Ask for page ${args.page + 1}.`);\n }\n if (editions.length === 0 && args.page > 1) {\n notes.push(\n `Page ${args.page} holds no row, which means the list ended earlier rather than that the work has no editions. Ask for page 1 to see it from the start.`,\n );\n } else if (editions.length === 0) {\n // A person, a subject heading and an address the BnF describes nowhere\n // all answer with no editions, and so does a work the catalogue links\n // none to. Explaining the empty list as a property of a work record\n // before the address is known to name one turns a wrong identifier into\n // a bibliographic fact.\n await assertWork(client, id);\n notes.push(\n `The catalogue links no published edition to \"${id.id}\". A work record can exist with none: it happens on provisional records, and on works the BnF describes through an anthology rather than through an edition of their own. Check get_work to see what kind of record this is.`,\n );\n }\n if (editions.length > 0) {\n notes.push(\n \"Rows are ordered by the address the catalogue gives each edition. It is neither chronological nor an order of importance, so a first edition can appear anywhere in the list. That order is the one the pages are cut along, so reading page after page reaches every edition once.\",\n );\n }\n\n const body =\n editions.length === 0\n ? nothingOnThisPage(args.page, id.id)\n : `${editions.length} edition(s) of \"${id.id}\":\\n${editions\n .map((edition, index) => {\n const imprint = [edition.place, edition.publisher, edition.date]\n .filter(Boolean)\n .join(\" : \");\n const extra = [\n edition.edition_statement,\n edition.extent,\n edition.isbn ? `ISBN ${edition.isbn}` : \"\",\n ]\n .filter(Boolean)\n .join(\" · \");\n const links =\n edition.digitised.length > 0\n ? `\\n digitised: ${edition.digitised.map((link) => `${link.role} ${link.url}`).join(\" \")}`\n : \"\";\n return `${index + 1}. ${edition.title ?? edition.id}${imprint ? ` · ${imprint}` : \"\"}${extra ? `\\n ${extra}` : \"\"}\\n ${edition.catalogue_url ?? edition.source_url}${links}`;\n })\n .join(\"\\n\")}`;\n\n return ok(\n {\n work_id: id.id,\n editions,\n page: args.page,\n has_more: data.hasMore,\n digitised_count: digitisedCount,\n retrieved_at: retrievedAt,\n notes,\n },\n body,\n { retrievedAt, notes },\n );\n } catch (error) {\n return toToolError(error);\n }\n}\n\n/** Refuse an address the catalogue describes as anything other than a work. */\nasync function assertWork(client: BnfClient, id: EntityId): Promise<void> {\n // Only a work is addressed under temp-work, so the address settles it and no\n // query is spent.\n if (id.kind === \"temp-work\") {\n return;\n }\n\n const types = await client.types(id);\n if (types.data.length === 0) {\n throw notFound(`data.bnf.fr describes nothing at \"${id.id}\".`, {\n hint: \"Find the identifier with search_works, or with list_works for the works of one person.\",\n url: id.pageUrl,\n });\n }\n if (recordKindOf(types.data) !== \"work\") {\n throw notFound(\n `\"${id.id}\" is described by data.bnf.fr, and it is not a work: it is typed ${types.data.join(\", \")}.`,\n {\n hint: \"This tool reads the editions of a work. For a person, list_works reaches the works they are credited with and get_author reads their record.\",\n url: id.pageUrl,\n },\n );\n }\n}\n","/**\n * list_works: the works the BnF catalogue names one person the creator of.\n *\n * The catalogue holds that link, so the question \"what did this person write\"\n * has an answer here. What it does not hold is a bibliography. A person is\n * credited on records in ways this link does not carry, and the BnF holds\n * printed editions whose work it has never established as an entry of its own,\n * so the list is what the link reaches and the answer says so.\n *\n * Two further things the catalogue withholds shape the rest. It points a work\n * at a term of its work-form vocabulary and publishes no label for that term\n * here, so the codes travel as they stand; and it states the form of some works\n * and not of others, which is what stops a code from being a filter: rows\n * carrying it are works declaring that form, and the rows without it are silent\n * rather than negative. And it orders nothing: the rows come back by the\n * address of the work, which is stable enough to page through and means nothing\n * else.\n */\n\nimport { z } from \"zod\";\nimport type { BnfClient } from \"../bnf/client.js\";\nimport type { EntityId } from \"../bnf/sparql.js\";\nimport { notFound } from \"../errors.js\";\nimport { parseEntityId } from \"../bnf/sparql.js\";\nimport { strictInput } from \"./arguments.js\";\nimport {\n FORMS_CAVEAT,\n FORMS_FILTER_CAVEAT,\n PROVISIONAL_CAVEAT,\n SEVERAL_RECORDS_CAVEAT,\n classifyEmptyPage,\n ok,\n retrievedAtSchema,\n toToolError,\n} from \"./shared.js\";\nimport type { EmptyPage, ToolResult } from \"./shared.js\";\n\nconst PERSON_TYPE = \"http://xmlns.com/foaf/0.1/Person\";\n\n/**\n * What the list holds, stated wherever it reaches a caller.\n *\n * It travels with every answer, including the ones holding no row: a page of\n * nothing is a statement about this link and about no other way the catalogue\n * credits a person, and reading it as a person having written nothing is the\n * one mistake this tool can cause.\n */\nconst SCOPE =\n \"A work is on this list because the BnF catalogue names this person as the creator of it. That is one link in the catalogue and not a bibliography: the catalogue credits a person on a record in other ways, and the BnF holds printed editions whose work it has never established as a record of its own.\";\n\n/** Wording used wherever a work's date reaches a caller. */\nconst DATE_CAVEAT =\n \"'date' and 'year' are the date of the work as the record gives it, which dates the work rather than any printing of it. A provisional record is made from a title the BnF has catalogued, so its date can be that of the printing it was made from. For the year a copy was printed, read list_editions.\";\n\n/** Wording used wherever the order of this list reaches a caller. */\nconst ORDER_CAVEAT =\n \"Rows are ordered by the address the catalogue gives each work, which is neither chronological nor an order of importance, so a first work can appear anywhere in the list. That order is the one the pages are cut along, so reading page after page reaches every work once.\";\n\nexport const listWorksDescription = [\n \"List the works the Bibliothèque nationale de France catalogue names one person the creator of, by the identifier search_authors returns. This is the tool for 'what did this person write'.\",\n \"Each row carries the title, the date the record gives the work, the form codes the catalogue points at, whether the record is established or provisional, and the identifier get_work, list_editions and find_digitised take.\",\n \"It is not a bibliography. It reports one link the catalogue holds, and the catalogue credits a person on a record in other ways and holds editions whose work it has never established, so a work missing here is not a work the person did not write.\",\n \"The form codes are the work-form vocabulary's own terms and carry no label in this dataset, so some read as words and some do not. A work stating no code has a form the catalogue does not state, so keeping the rows carrying one code finds the works that declare it and never all the works of that form.\",\n \"Rows are ordered by the address the catalogue gives each work, which is neither chronological nor an order of importance. No total is reported, because the catalogue counts nothing here and a count would read as a measure of what the person wrote.\",\n].join(\" \");\n\nexport const listWorksInput = strictInput({\n author_id: z\n .string()\n .min(1)\n .max(200)\n .describe(\n \"The person identifier from search_authors, such as 'cb119219976'. A full data.bnf.fr address is accepted too.\",\n ),\n limit: z.number().int().min(1).max(50).default(10),\n page: z.number().int().min(1).max(100).default(1),\n});\n\nexport const authoredWorkRowSchema = z.object({\n id: z.string().describe(\"Pass this to get_work, list_editions or find_digitised.\"),\n title: z.string().nullable(),\n date: z\n .string()\n .nullable()\n .describe(\n \"The date the record gives the work, as published. It dates the work rather than an edition of it.\",\n ),\n year: z.number().int().nullable().describe(\"The same date as a number, when the record has one.\"),\n forms: z\n .array(z.string())\n .describe(\n \"Terms of the BnF work-form vocabulary, as the terms themselves. The vocabulary publishes no label for them here, so they are not translated: some read as words, such as 'roman', and some do not, such as 'te'. An empty list is a form the record does not state, not a work without one.\",\n ),\n status: z\n .enum([\"established\", \"provisional\"])\n .describe(\n \"'established' is a work the BnF has settled as a record of its own. 'provisional' is a title it holds while a cataloguer settles it, whose identifier can change.\",\n ),\n source_url: z.string(),\n});\n\nexport const listWorksOutput = z.object({\n author_id: z.string(),\n works: z.array(authoredWorkRowSchema),\n page: z.number().int(),\n has_more: z.boolean(),\n retrieved_at: retrievedAtSchema,\n notes: z.array(z.string()),\n});\n\nexport type ListWorksArgs = z.infer<typeof listWorksInput>;\n\n/**\n * What a page holding no row is allowed to say.\n *\n * A page past where the rows stop and a person the catalogue links no work to\n * arrive identically, and calling either one an absence states something the\n * reading did not establish. Since no total is asked for, the only way to\n * separate them is to read the listing from its first row.\n */\nfunction emptyPageNote(emptiness: EmptyPage, page: number, authorId: string): string {\n if (emptiness === \"past_the_end\") {\n return `Page ${page} holds no row because it sits past the last row of this listing. The catalogue does link works to \"${authorId}\", and they are on earlier pages: call again with page=1, or with a lower page number, to read them.`;\n }\n if (emptiness === \"undetermined\") {\n return `Page ${page} holds no row, and the first page of the same listing could not be read to find out why. This is either the catalogue linking no work to \"${authorId}\", or a page sitting past the last row of a listing that does hold some. Call again with page=1: rows there mean the second.`;\n }\n return `The BnF catalogue links no work to \"${authorId}\" as its creator. The record exists and this link is empty on it, which is a statement about the catalogue rather than about what the person wrote. ${SEVERAL_RECORDS_CAVEAT}`;\n}\n\nfunction emptyPageBody(emptiness: EmptyPage, page: number, authorId: string): string {\n if (emptiness === \"past_the_end\") {\n return `Page ${page} of the works linked to \"${authorId}\" holds no row: it sits past the last row. Call again with page=1.`;\n }\n if (emptiness === \"undetermined\") {\n return `Page ${page} of the works linked to \"${authorId}\" holds no row, and whether the rows stop before it could not be read. Call again with page=1.`;\n }\n return `The BnF catalogue links no work to \"${authorId}\" as its creator.`;\n}\n\nexport async function runListWorks(client: BnfClient, args: ListWorksArgs): Promise<ToolResult> {\n try {\n const id = parseEntityId(args.author_id);\n const offset = (args.page - 1) * args.limit;\n const { data, cached, retrievedAt } = await client.worksByAuthor(id, args.limit, offset);\n\n const notes: string[] = [];\n if (cached) {\n notes.push(\"Served from this server's short-lived in-memory cache.\");\n }\n\n const works = data.rows.map((row) => ({\n id: row.id,\n title: row.title,\n date: row.date,\n year: row.year,\n forms: row.forms,\n status: row.status,\n source_url: row.sourceUrl,\n }));\n\n // The notes are ordered by how much a caller loses without them, because\n // the text block fits a limited trailer and drops the last of them to keep\n // the credit. What the list is comes first, then what to do next, then the\n // qualifications a caller could also read off the tool's own description.\n notes.push(SCOPE);\n\n if (data.skipped !== undefined && data.skipped > 0) {\n notes.push(\n `${data.skipped} row(s) the endpoint sent could not be read, because each named a record at an address this client cannot address. The page is short by that much rather than the catalogue holding less.`,\n );\n }\n\n let emptiness: EmptyPage = \"absent\";\n if (works.length === 0) {\n emptiness = await classifyEmptyPage(args.page, async () => {\n const first = await client.worksByAuthor(id, 1, 0);\n return first.data.rows.length > 0;\n });\n // A listing with no row anywhere says nothing until the address is known\n // to name a person. An edition, a subject heading and a record the BnF\n // describes nowhere all answer with no rows, and reporting any of them as\n // a person credited with nothing states a fact about a life the catalogue\n // never described.\n if (emptiness === \"absent\") {\n await assertPerson(client, id);\n }\n notes.push(emptyPageNote(emptiness, args.page, id.id));\n } else {\n if (data.hasMore) {\n notes.push(\n `More works exist beyond this page. Ask for page ${args.page + 1}. This server reports no total: a count of the works one link reaches would read as a count of what the person wrote, and the catalogue does not carry that.`,\n );\n }\n notes.push(`${FORMS_CAVEAT} ${FORMS_FILTER_CAVEAT}`);\n if (works.some((work) => work.status === \"provisional\")) {\n notes.push(PROVISIONAL_CAVEAT);\n }\n notes.push(ORDER_CAVEAT, DATE_CAVEAT);\n }\n\n const body =\n works.length === 0\n ? emptyPageBody(emptiness, args.page, id.id)\n : `${works.length} work(s) the catalogue links to \"${id.id}\" as their creator:\\n${works\n .map((work, index) => {\n const when = work.date ? ` (${work.date})` : \"\";\n const forms = work.forms.length > 0 ? ` · form: ${work.forms.join(\", \")}` : \"\";\n const status = work.status === \"provisional\" ? \" · provisional record\" : \"\";\n return `${index + 1}. ${work.title ?? work.id}${when}${forms}${status} · id: ${work.id}\\n ${work.source_url}`;\n })\n .join(\"\\n\")}`;\n\n return ok(\n {\n author_id: id.id,\n works,\n page: args.page,\n has_more: data.hasMore,\n retrieved_at: retrievedAt,\n notes,\n },\n body,\n { retrievedAt, notes, sourceUrl: id.pageUrl },\n );\n } catch (error) {\n return toToolError(error);\n }\n}\n\n/** Refuse an address the catalogue describes as anything other than a person. */\nasync function assertPerson(client: BnfClient, id: EntityId): Promise<void> {\n const types = await client.types(id);\n if (types.data.length === 0) {\n throw notFound(`data.bnf.fr describes nothing at \"${id.id}\".`, {\n hint: \"Find the identifier with search_authors rather than writing one.\",\n url: id.pageUrl,\n });\n }\n if (!types.data.includes(PERSON_TYPE)) {\n throw notFound(\n `\"${id.id}\" is described by data.bnf.fr, and it is not a person: it is typed ${types.data.join(\", \")}.`,\n {\n hint: \"This tool walks from a person to the works they are credited with. For a work, list_editions reads its editions and get_work reads the record.\",\n url: id.pageUrl,\n },\n );\n }\n}\n","/**\n * search_authors: find a person in the BnF authority file by name.\n *\n * One person can hold several authority records. \"Arthur Rimbaud\" answers with\n * two, `cb119219976` and `cb11921998j`, and neither is a mistake: the file\n * distinguishes the poet from a second heading the BnF keeps for him. Returning\n * one of the two and calling it the answer would hide a choice the caller is\n * the only one able to make, so every match is returned and the answer says\n * when several rows carry one name.\n */\n\nimport { z } from \"zod\";\nimport type { BnfClient } from \"../bnf/client.js\";\nimport { TEXT_WINDOW } from \"../bnf/queries.js\";\nimport { invalidInput } from \"../errors.js\";\nimport { readSearchText } from \"../bnf/sparql.js\";\nimport { strictInput } from \"./arguments.js\";\nimport {\n NO_RANKING,\n WINDOW_FULL_AND_NO_MORE,\n WINDOW_FULL_CAVEAT,\n classifyEmptyPage,\n headingYearConflicts,\n ok,\n readingNotes,\n retrievedAtSchema,\n toToolError,\n} from \"./shared.js\";\nimport type { EmptyPage, ToolResult } from \"./shared.js\";\n\n/** The joiners a name carries, which an index may or may not keep. */\nconst JOINER = /['-]/u;\n\nexport const searchAuthorsDescription = [\n \"Find a person in the Bibliothèque nationale de France authority file by name, and get the identifier the other tools take.\",\n \"This matches the name the BnF records, so it takes a surname, a full name or both names in either order. It does not read biographies, so it cannot find a person from what they wrote or what they did.\",\n \"It reads the person records and nothing else: an organisation, a conference or a place is outside it, and an answer holding no row says nothing about those.\",\n \"The match is letter for letter, so a name written with other accents or under another transliteration is a different search: try the spellings a library would use before concluding the BnF holds nobody of that name.\",\n \"Several rows can carry one name: the BnF keeps more than one authority record for some people, and many people share a name. Read 'birth_year', 'death_year' and 'role' to tell them apart, and show the caller the choice rather than picking one.\",\n \"Rows come back ordered by the address of the record, which is not an order of relevance. That order is the one the pages are cut along, so paging through a search reaches every match once.\",\n \"One search reads a fixed window of the index. 'index_window_full' says whether that window came back full: when it did, names sit past what was read, 'has_more' being false says where the reading stopped, and another reading of the same search can bring back other rows. Narrow the name to reach further.\",\n].join(\" \");\n\nexport const searchAuthorsInput = strictInput({\n name: z\n .string()\n .min(1)\n .max(200)\n .describe(\"The person's name, such as 'Rimbaud' or 'Arthur Rimbaud'.\"),\n limit: z.number().int().min(1).max(50).default(10),\n page: z.number().int().min(1).max(100).default(1),\n});\n\nexport const authorRowSchema = z.object({\n id: z.string().describe(\"Pass this to get_author, or to find_digitised.\"),\n name: z.string().nullable().describe(\"The name as data.bnf.fr writes it.\"),\n label: z\n .string()\n .nullable()\n .describe(\"The authority heading, which usually carries the dates in brackets.\"),\n birth_year: z.number().int().nullable(),\n death_year: z.number().int().nullable().describe(\"Null when the record states none.\"),\n role: z\n .string()\n .nullable()\n .describe(\n \"What the record says the person did. This field holds a job title far more often than a sentence, and is frequently a single word.\",\n ),\n source_url: z.string().describe(\"The record's page. Show this when citing it.\"),\n});\n\nexport const searchAuthorsOutput = z.object({\n name: z.string().describe(\"The name asked for.\"),\n words_searched: z\n .array(z.string())\n .describe(\n \"The terms the index required, each of which has to appear in the name. A word is cut at an apostrophe and at a hyphen before the index sees it, so a name written as one word can appear here as two, and a record carrying the pieces apart is a match.\",\n ),\n authors: z.array(authorRowSchema),\n page: z.number().int(),\n has_more: z\n .boolean()\n .describe(\n \"Whether the endpoint held at least one further row beyond this page, within the window the search read off the index. Read it alongside 'index_window_full': false on a full window says where the reading stopped rather than that the file holds nobody else.\",\n ),\n index_window_full: z\n .boolean()\n .nullable()\n .describe(\n \"Whether the fixed window this search reads off the index came back full. True means records carrying these words sit past what was read, so the rows here are a part of the matches and another reading can bring back other rows. False means the window held everything that matched. Null when the endpoint stated no occupancy, which claims nothing either way.\",\n ),\n retrieved_at: retrievedAtSchema,\n notes: z.array(z.string()),\n});\n\nexport type SearchAuthorsArgs = z.infer<typeof searchAuthorsInput>;\n\n/** Wording used wherever the spelling of a name decides what a search reaches. */\nconst SPELLING_CAVEAT =\n \"The index matches the words letter for letter, so a name written with other accents, or under another transliteration, is a different search reaching different records. A short answer is no evidence that the file holds nobody else of that name: ask again with the accents dropped, with them restored, and under the transliterations a library uses.\";\n\n/**\n * The years a record states, written so that a silence stays a silence.\n *\n * Printing \"1940–?\" hands a reader a glyph to interpret, and most read it as a\n * death on a date nobody wrote down. A record carrying a birth year and no death\n * year says one thing: when the person was born.\n */\nfunction lifespan(birthYear: number | null, deathYear: number | null): string {\n if (birthYear !== null && deathYear !== null) {\n return ` (${birthYear}–${deathYear})`;\n }\n if (birthYear !== null) {\n return ` (born ${birthYear})`;\n }\n if (deathYear !== null) {\n return ` (died ${deathYear})`;\n }\n return \"\";\n}\n\n/**\n * What a page of no rows is allowed to say.\n *\n * Only a search whose first row is missing too is an absence. A page past where\n * the rows stop is a fact about the page asked for, and it is written as such:\n * the page, that it holds nothing, and the way back to the rows. No count of\n * what came before appears, because this server never asks the endpoint how\n * many records match, so where the rows stop is a boundary it can point at and\n * not a number it can state.\n */\nfunction emptyPageNote(\n emptiness: EmptyPage,\n page: number,\n words: string[],\n windowFull: boolean,\n): string {\n const quoted = words.map((word) => `\"${word}\"`).join(\", \");\n // A window filled by records of another kind leaves the filter nothing to\n // keep, and the page that comes back is the same empty page an unmatched name\n // produces. Only one of the two is an absence, and the window says which.\n if (windowFull) {\n return `The window this search reads off the index came back full, and no person record survived the reading: every row of it named something the authority file types as another kind of heading. Whether a person carries ${quoted} in a name is unanswered here rather than answered no. Narrow the search, by adding a word or by writing a fuller form of the name, so that the window reaches further into the index.`;\n }\n if (emptiness === \"past_the_end\") {\n return `Page ${page} holds no row because it sits past the last row of this search. Records do carry ${quoted} in a name, and they are on earlier pages: call again with page=1, or with a lower page number, to read them.`;\n }\n if (emptiness === \"undetermined\") {\n return `Page ${page} holds no row, and reading the first page of the same search to find out why did not answer. This is either a name carrying ${quoted} matching no record, or a page sitting past the last row of a search that does match. Call again with page=1: rows there mean the second.`;\n }\n return `No person record in the BnF authority file carries every one of these words in a name: ${quoted}. This search reads the person records and nothing else, so an organisation, a conference, a place or a subject heading is outside what was looked at, whether or not the BnF holds a heading for one. It reads names, so a person known by a pen name is found under that name rather than their own.`;\n}\n\nfunction emptyPageBody(\n emptiness: EmptyPage,\n page: number,\n name: string,\n windowFull: boolean,\n): string {\n if (windowFull) {\n return `No person record survived the reading for \"${name}\", out of a window of the index that came back full. Narrow the search.`;\n }\n if (emptiness === \"past_the_end\") {\n return `Page ${page} of the search for \"${name}\" holds no row: it sits past the last row. Call again with page=1.`;\n }\n if (emptiness === \"undetermined\") {\n return `Page ${page} of the search for \"${name}\" holds no row, and whether the rows stop before it could not be read. Call again with page=1.`;\n }\n return `No person in the BnF authority file matches \"${name}\".`;\n}\n\nexport async function runSearchAuthors(\n client: BnfClient,\n args: SearchAuthorsArgs,\n): Promise<ToolResult> {\n try {\n const reading = readSearchText(args.name);\n const words = reading.words;\n if (words.length === 0) {\n return toToolError(\n invalidInput(\n `\"${args.name}\" holds no word to search for.`,\n \"Write the name in letters, such as 'Rimbaud'.\",\n ),\n );\n }\n\n // A text search reads a fixed window of the index, and a page beyond it\n // would come back empty for want of anything having been read. Answering\n // that with \"the BnF holds none\" states an absence nobody established, so\n // the page is refused here rather than bought from the endpoint.\n const offset = (args.page - 1) * args.limit;\n if (offset + args.limit > TEXT_WINDOW) {\n return toToolError(\n invalidInput(\n `Page ${args.page} at ${args.limit} rows a page reaches past row ${TEXT_WINDOW}, which is as far into the index as one search reads.`,\n `Narrow the search instead. Rows past ${TEXT_WINDOW} are not a smaller part of the answer: they were never read, so an empty page there would say nothing about the catalogue.`,\n ),\n );\n }\n const { data, cached, retrievedAt } = await client.searchAuthors(args.name, args.limit, offset);\n\n const terms = reading.terms;\n const windowFull = data.indexWindowFull ?? null;\n\n const notes: string[] = [];\n if (cached) {\n notes.push(\"Served from this server's short-lived in-memory cache.\");\n }\n\n // What bounds the answer is said before what qualifies it. The text block\n // fits a limited trailer and drops the last notes to keep the credit, and a\n // caller losing this one is left reading a part of the matches as all of\n // them.\n if (windowFull) {\n notes.push(WINDOW_FULL_CAVEAT);\n if (!data.hasMore) {\n notes.push(WINDOW_FULL_AND_NO_MORE);\n }\n }\n\n // A caller checking why a row is on the list reads the terms, so the ones\n // the index made out of a word are traced back to it before anything that\n // merely qualifies the answer.\n const split = words.filter((word) => JOINER.test(word));\n if (split.length > 0) {\n notes.push(\n `${split.map((word) => `\"${word}\"`).join(\", \")} reached the index as separate terms: it cuts a word at an apostrophe and at a hyphen, and requires each piece to appear in the name rather than in that arrangement, so a record writing the pieces apart is a match.`,\n );\n }\n notes.push(...readingNotes(reading, \"name\"));\n\n const authors = data.rows.map((row) => ({\n id: row.id,\n name: row.name,\n label: row.label,\n birth_year: row.birthYear,\n death_year: row.deathYear,\n role: row.role,\n source_url: row.sourceUrl,\n }));\n\n if (authors.length > 0) {\n notes.push(NO_RANKING);\n }\n\n // The index compares the characters it was given against the characters a\n // cataloguer entered. A name the BnF holds under a transliteration of its\n // own is therefore reachable only by that spelling, and the answer to any\n // other one is a short list or none at all, neither of which looks partial.\n notes.push(SPELLING_CAVEAT);\n\n const disputed = authors\n .map((author) => ({\n label: author.label ?? author.name,\n conflicts: headingYearConflicts(author.label, author.birth_year, author.death_year),\n }))\n .filter((row) => row.conflicts.length > 0);\n if (disputed.length > 0) {\n notes.push(\n `${disputed.map((row) => `\"${row.label}\" states ${row.conflicts.join(\", and \")}`).join(\"; \")}. A record stating its dates twice can disagree with itself, and the BnF publishes both. These fields are what tells two people of one name apart, so open source_url before telling them apart by a date the record disputes.`,\n );\n }\n\n // Several records under one name is the case this tool exists to surface.\n const repeated = new Map<string, number>();\n for (const author of authors) {\n const key = (author.name ?? \"\").toLocaleLowerCase(\"fr\");\n if (key !== \"\") {\n repeated.set(key, (repeated.get(key) ?? 0) + 1);\n }\n }\n const shared = [...repeated.entries()].filter(([, count]) => count > 1);\n if (shared.length > 0) {\n notes.push(\n `${shared.map(([name, count]) => `${count} records are named \"${name}\"`).join(\"; \")}. The BnF keeps more than one authority record for some people, and different people share names. Read the dates and the role before treating any of them as the person meant.`,\n );\n }\n\n if (data.hasMore) {\n notes.push(\n `More matches exist beyond this page. Ask for page ${args.page + 1}, or narrow the name. This server reports no total, because counting every match means asking the endpoint to walk the whole index a second time.`,\n );\n }\n // A page holding no row is read for what it is before anything is said\n // about the authority file: no name matching, and the rows of a name that\n // does match stopping earlier, look identical from the page alone.\n let emptiness: EmptyPage = \"absent\";\n if (authors.length === 0) {\n // A full window already accounts for the page, and reading the search\n // from its first row would answer a question the window has settled.\n emptiness = windowFull\n ? \"absent\"\n : await classifyEmptyPage(args.page, async () => {\n const first = await client.searchAuthors(args.name, 1, 0);\n return first.data.rows.length > 0;\n });\n notes.push(emptyPageNote(emptiness, args.page, terms, windowFull === true));\n }\n\n const body =\n authors.length === 0\n ? emptyPageBody(emptiness, args.page, args.name, windowFull === true)\n : `${authors.length} record(s) for \"${args.name}\":\\n${authors\n .map((author, index) => {\n const life = lifespan(author.birth_year, author.death_year);\n const role = author.role ? ` · ${author.role}` : \"\";\n return `${index + 1}. ${author.name ?? author.label ?? author.id}${life}${role} · id: ${author.id}\\n ${author.source_url}`;\n })\n .join(\"\\n\")}`;\n\n return ok(\n {\n name: args.name,\n words_searched: terms,\n authors,\n page: args.page,\n has_more: data.hasMore,\n index_window_full: windowFull,\n retrieved_at: retrievedAt,\n notes,\n },\n body,\n { retrievedAt, notes },\n );\n } catch (error) {\n return toToolError(error);\n }\n}\n","/**\n * search_works: find a work by words in its title.\n *\n * The endpoint's full-text index answers one question: does this title carry\n * every one of these words. It returns no score, and the rows come back in the\n * order of the record's address, which measures nothing about the match.\n * Searching for \"saison enfer\" therefore returns a dozen studies of Rimbaud\n * before Rimbaud, and every one of them is a correct match.\n *\n * So this tool never presents its rows as a ranking, and never presents the\n * first row as the answer. Saying that plainly is worth more than any\n * re-ordering this server could invent, since an order this server made up\n * would be a claim the catalogue does not support.\n */\n\nimport { z } from \"zod\";\nimport type { BnfClient } from \"../bnf/client.js\";\nimport { TEXT_WINDOW } from \"../bnf/queries.js\";\nimport { invalidInput } from \"../errors.js\";\nimport { readSearchText } from \"../bnf/sparql.js\";\nimport { strictInput } from \"./arguments.js\";\nimport {\n NO_RANKING,\n PROVISIONAL_CAVEAT,\n WINDOW_FULL_AND_NO_MORE,\n WINDOW_FULL_CAVEAT,\n classifyEmptyPage,\n ok,\n readingNotes,\n retrievedAtSchema,\n toToolError,\n} from \"./shared.js\";\nimport type { EmptyPage, ToolResult } from \"./shared.js\";\n\n/** The joiners a name carries, which an index may or may not keep. */\nconst JOINER = /['-]/u;\n\nexport const searchWorksDescription = [\n \"Find a work in the Bibliothèque nationale de France catalogue by words in its title, and get the identifier get_work and list_editions take.\",\n \"A row matches when its title carries every word given. The index returns no measure of how well a row matches, so the rows are ordered by the address of the record and the work a person would call the obvious answer can sit anywhere in the list or on a later page.\",\n \"A study of a book carries the book's title, so a search for a famous title returns the criticism alongside the work. Read 'creators' to tell them apart.\",\n \"This reads titles and nothing else. Searching a person's name returns the works written about them, whose creators are their critics; list_works is what walks from a person to the works the catalogue names them the creator of.\",\n \"'status' says whether the BnF has established the work as a record of its own or holds it provisionally under a title it has catalogued.\",\n \"One search reads a fixed window of the index, and that window is read before the works are told apart from everything else the index holds, so even a short list can rest on a full one. 'index_window_full' says which: when it is true, titles sit past what was read, 'has_more' being false says where the reading stopped, and another reading of the same search can bring back other rows. Add a word to the title to reach further.\",\n].join(\" \");\n\nexport const searchWorksInput = strictInput({\n title: z\n .string()\n .min(1)\n .max(200)\n .describe(\"Words from the title, such as 'saison enfer'. Every word given has to appear.\"),\n limit: z.number().int().min(1).max(50).default(10),\n page: z.number().int().min(1).max(100).default(1),\n});\n\nexport const workRowSchema = z.object({\n id: z.string().describe(\"Pass this to get_work, list_editions or find_digitised.\"),\n title: z.string().nullable(),\n date: z.string().nullable().describe(\"The year the record gives the work, as published.\"),\n creators: z\n .array(z.object({ id: z.string(), name: z.string().nullable() }))\n .describe(\"Everyone the record credits. Empty when the record credits nobody.\"),\n status: z\n .enum([\"established\", \"provisional\"])\n .describe(\n \"'established' is a work the BnF has settled as a record of its own. 'provisional' is a title it holds while a cataloguer settles it, whose identifier can change.\",\n ),\n source_url: z.string(),\n});\n\nexport const searchWorksOutput = z.object({\n title: z.string().describe(\"The words asked for.\"),\n words_searched: z\n .array(z.string())\n .describe(\n \"The terms the index required, each of which has to appear in the title. A word is cut at an apostrophe and at a hyphen before the index sees it, so a word given as one can appear here as two, and a record carrying the pieces apart is a match.\",\n ),\n works: z.array(workRowSchema),\n page: z.number().int(),\n has_more: z\n .boolean()\n .describe(\n \"Whether the endpoint held at least one further row beyond this page, within the window the search read off the index. Read it alongside 'index_window_full': false on a full window says where the reading stopped rather than that the catalogue holds nothing else.\",\n ),\n index_window_full: z\n .boolean()\n .nullable()\n .describe(\n \"Whether the fixed window this search reads off the index came back full. The window is read before the works are separated from the rest of the index, so a short list can rest on a full window. True means titles carrying these words sit past what was read, and another reading can bring back other rows. False means the window held everything that matched. Null when the endpoint stated no occupancy.\",\n ),\n retrieved_at: retrievedAtSchema,\n notes: z.array(z.string()),\n});\n\nexport type SearchWorksArgs = z.infer<typeof searchWorksInput>;\n\n/**\n * What a page of no rows is allowed to say.\n *\n * Only a search whose first row is missing too is an absence. A page past where\n * the rows stop is a fact about the page asked for, and it is written as such:\n * the page, that it holds nothing, and the way back to the rows. No count of\n * what came before appears, because this server never asks the endpoint how\n * many records match, so where the rows stop is a boundary it can point at and\n * not a number it can state.\n */\nfunction emptyPageNote(\n emptiness: EmptyPage,\n page: number,\n words: string[],\n windowFull: boolean,\n): string {\n const quoted = words.map((word) => `\"${word}\"`).join(\", \");\n // The window is read before works are told apart from the rest of the index,\n // so a full one can leave nothing for the answer while the catalogue holds\n // matching works further along. That page and a page of no matches at all are\n // the same page, and the window is what separates them.\n if (windowFull) {\n return `The window this search reads off the index came back full, and no work record survived the reading: every row of it named something the catalogue types as another kind of record. Whether a work carries ${quoted} in its title is unanswered here rather than answered no. Add a word to the title so that the window reaches further into the index.`;\n }\n if (emptiness === \"past_the_end\") {\n return `Page ${page} holds no row because it sits past the last row of this search. Works do carry ${quoted} in their title, and they are on earlier pages: call again with page=1, or with a lower page number, to read them.`;\n }\n if (emptiness === \"undetermined\") {\n return `Page ${page} holds no row, and reading the first page of the same search to find out why did not answer. This is either a title carrying ${quoted} matching no record, or a page sitting past the last row of a search that does match. Call again with page=1: rows there mean the second.`;\n }\n return `No work in the BnF catalogue has a title carrying every one of these words: ${quoted}. Every word given has to appear, so dropping one widens the search.`;\n}\n\nfunction emptyPageBody(\n emptiness: EmptyPage,\n page: number,\n title: string,\n windowFull: boolean,\n): string {\n if (windowFull) {\n return `No work record survived the reading for \"${title}\", out of a window of the index that came back full. Add a word to the title.`;\n }\n if (emptiness === \"past_the_end\") {\n return `Page ${page} of the search for \"${title}\" holds no row: it sits past the last row. Call again with page=1.`;\n }\n if (emptiness === \"undetermined\") {\n return `Page ${page} of the search for \"${title}\" holds no row, and whether the rows stop before it could not be read. Call again with page=1.`;\n }\n return `No work in the BnF catalogue matches \"${title}\".`;\n}\n\nexport async function runSearchWorks(\n client: BnfClient,\n args: SearchWorksArgs,\n): Promise<ToolResult> {\n try {\n const reading = readSearchText(args.title);\n const words = reading.words;\n if (words.length === 0) {\n return toToolError(\n invalidInput(\n `\"${args.title}\" holds no word to search for.`,\n \"Write the words of the title in letters, such as 'saison enfer'.\",\n ),\n );\n }\n\n // A text search reads a fixed window of the index, and a page beyond it\n // would come back empty for want of anything having been read. Answering\n // that with \"the BnF holds none\" states an absence nobody established, so\n // the page is refused here rather than bought from the endpoint.\n const offset = (args.page - 1) * args.limit;\n if (offset + args.limit > TEXT_WINDOW) {\n return toToolError(\n invalidInput(\n `Page ${args.page} at ${args.limit} rows a page reaches past row ${TEXT_WINDOW}, which is as far into the index as one search reads.`,\n `Narrow the search instead. Rows past ${TEXT_WINDOW} are not a smaller part of the answer: they were never read, so an empty page there would say nothing about the catalogue.`,\n ),\n );\n }\n const { data, cached, retrievedAt } = await client.searchWorks(args.title, args.limit, offset);\n\n const terms = reading.terms;\n const windowFull = data.indexWindowFull ?? null;\n\n const notes: string[] = [];\n if (cached) {\n notes.push(\"Served from this server's short-lived in-memory cache.\");\n }\n\n // What bounds the answer is said before what qualifies it. The text block\n // fits a limited trailer and drops the last notes to keep the credit, and a\n // caller losing this one is left reading a part of the matches as all of\n // them.\n if (windowFull) {\n notes.push(WINDOW_FULL_CAVEAT);\n if (!data.hasMore) {\n notes.push(WINDOW_FULL_AND_NO_MORE);\n }\n }\n\n // A caller checking why a row is on the list reads the terms, so the ones\n // the index made out of a word are traced back to it before anything that\n // merely qualifies the answer.\n const split = words.filter((word) => JOINER.test(word));\n if (split.length > 0) {\n notes.push(\n `${split.map((word) => `\"${word}\"`).join(\", \")} reached the index as separate terms: it cuts a word at an apostrophe and at a hyphen, and requires each piece to appear in the title rather than in that arrangement, so a record writing the pieces apart is a match.`,\n );\n }\n notes.push(...readingNotes(reading, \"title\"));\n\n const works = data.rows.map((row) => ({\n id: row.id,\n title: row.title,\n date: row.date,\n creators: row.creators,\n status: row.status,\n source_url: row.sourceUrl,\n }));\n\n if (works.length > 0) {\n notes.push(NO_RANKING);\n }\n if (works.some((work) => work.status === \"provisional\")) {\n notes.push(PROVISIONAL_CAVEAT);\n }\n\n // Writing a title out in full narrows the search rather than sharpening it.\n // \"Une saison en enfer\" requires \"une\" and \"en\" to appear as well, so a\n // record catalogued as \"Saison en enfer\" falls out of the answer, and the\n // caller sees a shorter list and reads it as the BnF holding less. Which\n // words carry the meaning is not something this server can tell: \"mal\" and\n // \"les\" are the same length, so the requirement is stated and the choice of\n // what to drop is left where it belongs.\n if (terms.length > 2) {\n notes.push(\n `All ${terms.length} terms were required: ${terms.map((term) => `\"${term}\"`).join(\", \")}. A record catalogued under a shorter form of the title carries fewer of them and falls out of the answer, so dropping a word widens the search.`,\n );\n }\n\n // A page of studies about a famous work, with the work itself further down\n // the index, otherwise reads as the BnF not holding the work at all.\n if (works.length > 0 && works.every((work) => work.status === \"provisional\")) {\n notes.push(\n \"Every row on this page is a provisional record. The BnF creates one for a title it has catalogued without settling it as a work of its own, which is what a study of a famous book usually gets. An established record for the work itself can sit further down the index: ask for the next page, or search fewer words of the title.\",\n );\n }\n\n if (data.hasMore) {\n notes.push(\n `More matches exist beyond this page. Ask for page ${args.page + 1}, or add a word to the title. This server reports no total: a count would read as a measure of the search, and this search has none.`,\n );\n }\n // A page holding no row is read for what it is before anything is said\n // about the catalogue: the words matching nothing, and the rows of a search\n // that does match stopping earlier, look identical from the page alone.\n let emptiness: EmptyPage = \"absent\";\n if (works.length === 0) {\n // A full window already accounts for the page, and reading the search\n // from its first row would answer a question the window has settled.\n emptiness = windowFull\n ? \"absent\"\n : await classifyEmptyPage(args.page, async () => {\n const first = await client.searchWorks(args.title, 1, 0);\n return first.data.rows.length > 0;\n });\n notes.push(emptyPageNote(emptiness, args.page, terms, windowFull === true));\n }\n\n const body =\n works.length === 0\n ? emptyPageBody(emptiness, args.page, args.title, windowFull === true)\n : `${works.length} work(s) whose title carries ${terms.map((term) => `\"${term}\"`).join(\" and \")}:\\n${works\n .map((work, index) => {\n const by =\n work.creators.length > 0\n ? ` · ${work.creators.map((creator) => creator.name ?? creator.id).join(\", \")}`\n : \"\";\n const when = work.date ? ` (${work.date})` : \"\";\n const status = work.status === \"provisional\" ? \" · provisional record\" : \"\";\n return `${index + 1}. ${work.title ?? work.id}${when}${by}${status} · id: ${work.id}\\n ${work.source_url}`;\n })\n .join(\"\\n\")}`;\n\n return ok(\n {\n title: args.title,\n words_searched: terms,\n works,\n page: args.page,\n has_more: data.hasMore,\n index_window_full: windowFull,\n retrieved_at: retrievedAt,\n notes,\n },\n body,\n { retrievedAt, notes },\n );\n } catch (error) {\n return toToolError(error);\n }\n}\n"],"mappings":"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","names":["process","text","page","z","z","z","text","page","z","z","z","z","notesOnWhatTheRecordStates","z","z","page","z","z","PERSON_TYPE","z","page","z","z","emptyPageNote","page","emptyPageBody","z","JOINER","z","emptyPageNote","page","emptyPageBody","process"]}
|