mcp-databnf 1.0.1 → 1.1.1

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 CHANGED
@@ -2,6 +2,165 @@
2
2
 
3
3
  This project follows [semantic versioning](https://semver.org/).
4
4
 
5
+ ## 1.1.1
6
+
7
+ - The README carries the same badge row as every server here: npm, CI, the
8
+ licence, the MCP registry entry, the Glama score, and one-click installs for
9
+ Cursor and VS Code. Each install link encodes this package. npm serves the
10
+ README frozen at publish time, so a release is what puts it there.
11
+
12
+ ## 1.1.0
13
+
14
+ - A text search reads a fixed window of the index, and a search that filled it
15
+ reported exhaustion. `search_authors {"name":"Marie","limit":50,"page":8}`
16
+ returned fifty rows and `has_more: false`, while page 9 was refused for
17
+ reaching past the end of what one search reads: of the two statements, the one
18
+ a caller reaches by stopping where the server told them to was the false one.
19
+ The window is also read before the type filter, so a list of a hundred and
20
+ fifty rows could rest on a full one. The endpoint now sends the occupancy of
21
+ the window in the request that reads the page, and both searches publish
22
+ `index_window_full` and say in words what a full window means: the rows are a
23
+ part of the matches, and `has_more` false marks where the reading stopped. No
24
+ count of matches is reported, here or anywhere else.
25
+ - The rows a full window holds are settled by the index as it answers, so two
26
+ readings of one search could differ with nothing said. The same wording now
27
+ states it, and ties it to the window: an answer resting on a window with room
28
+ to spare holds every match and is repeatable.
29
+ - A page emptied by the filter behind a full window said that the BnF held
30
+ nobody, and no reading had established that. Both searches now name the
31
+ window as what the page rests on and leave the question open.
32
+ - `words_searched` named one term where the index had required two. It cuts a
33
+ word at an apostrophe and at a hyphen, so `O'Brien` is required as `O` and
34
+ `Brien` and a record written `Pat O Brien` matches. Both searches now report
35
+ the terms the index required, and say which word was cut, so a caller can see
36
+ from the fields why a row is on the list.
37
+ - `get_author` said a person might be living on a record that dates them
38
+ nowhere, about a man dead since 1881. The BnF keeps a second authority record
39
+ for some people, carrying a name and nothing else, and a silence on that
40
+ record is a fact about the record. The note now turns on whether the record
41
+ states a birth, and a record stating neither date says so and points at
42
+ `search_authors` as the way to the other records the file holds.
43
+ - `list_works` answering with no row now carries the same pointer, since the
44
+ link it reports hangs off a record rather than off a person.
45
+ - `get_work` printed the work-form codes with none of the caveat the listing
46
+ gives them. The wording is now shared, so both tools say that the vocabulary
47
+ publishes no label for those terms and that an absent code is a form the
48
+ catalogue does not state.
49
+ - `search_authors`, `search_works`, `list_editions` and `list_works` served one
50
+ record on two pages and left another out. Each cuts its page in a subquery and
51
+ then reads the columns in an outer query that asked for no order, and SPARQL
52
+ gives such a query no order at all: the endpoint sent the rows of a page in a
53
+ sequence of its own, and the row dropped to turn a page of eleven into a page
54
+ of ten was whichever one arrived last. Marguerite Duras at four works a page
55
+ put `cb12285962p` on pages 2 and 3 and never showed `cb122859600`. Every paged
56
+ query now sorts its outer form on the same address the page was cut along, so
57
+ a walk through the pages reaches each record once. The sort runs over the rows
58
+ of a single page, and the endpoint answers it in the same time as before.
59
+ - The note attached to a list of works claimed the order held between pages,
60
+ which was the guarantee the query had not asked for. Every wording about the
61
+ order now names what the rows are ordered by, and says that paging along it
62
+ reaches each record once.
63
+
64
+ - `list_works` walks from a person to the works the catalogue names them the
65
+ creator of, which is the question "what did this person write". A title search
66
+ cannot answer it: the index reads titles, so a person's name in a title belongs
67
+ to the books written about them, and searching "Balzac" returns his critics.
68
+ - The list states what it is. It reports one link, `dcterms:creator`, and the
69
+ catalogue credits a person on a record in other ways and holds editions whose
70
+ work it has never established, so an absent title is not a title the person did
71
+ not write. No total is reported: a count of what one link reaches would be read
72
+ as a count of what somebody wrote.
73
+ - Each row carries the form codes the catalogue points at, untranslated, because
74
+ this dataset publishes no label for that vocabulary.
75
+ - A page holding no row is read before anything is said about the catalogue. A
76
+ page past the last row points back to page 1, a person the link is empty on is
77
+ named as such, and an address the catalogue types as something other than a
78
+ person is refused with what it is.
79
+
80
+ - `list_editions` answered a person's identifier with an empty list of editions
81
+ and explained it as a property of work records, which invented a bibliographic
82
+ fact about a record that is not a work. It now reads what the address is
83
+ before explaining anything, and refuses one the catalogue types otherwise,
84
+ naming that type and pointing at `list_works` and `get_author`. A provisional
85
+ identifier still costs no query, since only a work is addressed under
86
+ `temp-work`.
87
+ - `find_digitised` followed the `kind` it was given without checking it, and
88
+ returned that same word as its own reading of the record. The person path and
89
+ the work path reach different links, so a wrong kind answered with part of the
90
+ record and nothing said so. The catalogue is now asked what the record is on
91
+ every call, `kind` in the answer is that reading, and a stated kind the
92
+ catalogue contradicts is refused as invalid input.
93
+ - The wording about work forms denied information the field carries. Some terms
94
+ of the vocabulary read as words and some do not, and what the catalogue
95
+ withholds is a form on the many works that state none. Every place that
96
+ describes them, the tool descriptions, the notes, the schemas and the README,
97
+ now says that: a code is what a work declares, an absent code is a form
98
+ unstated, and selecting on a code finds the works declaring it and never all
99
+ the works of that form.
100
+ - A person's record can state its dates twice, in the brackets of a heading and
101
+ in the dated fields, and the two can disagree. `get_author` and
102
+ `search_authors` now say when they do, quoting both, and settle nothing: those
103
+ fields are what tells two people of one name apart, and the record gives no
104
+ ground for preferring either side.
105
+ - `search_authors` matches the index letter for letter, so a name written with
106
+ other accents or under another transliteration is a different search reaching
107
+ different records, and a short list of rows looks exactly like a complete one.
108
+ Every answer now says so. An answer holding no row also said that no record in
109
+ the authority file carried the name, having read the person records alone; it
110
+ now states that scope, so an organisation or a conference is named as outside
111
+ what was searched.
112
+ - A character that marks nothing on a screen was treated as a word separator, so
113
+ a control character pasted inside a name cut it in two and the index was asked
114
+ for both halves. `Rimbaud` carrying one such byte returned no row and the
115
+ answer read "No person in the BnF authority file matches", which no reading had
116
+ established. The control characters, the zero-width spaces and joiners, the
117
+ soft hyphen, the bidirectional marks and the byte order mark are now removed
118
+ before the words are cut, so a word one of them sits inside is searched for
119
+ whole, and the answer says how many were removed. A space, a tab and a line
120
+ break still separate words.
121
+ - Punctuation reached no term while a one-letter fragment was kept as a term the
122
+ index required, and neither was said. Both searches now name the characters
123
+ they set aside beside the terms they asked for, and name a term of one
124
+ character as required like any other, since either can be what emptied a list.
125
+ - The addresses under `same_as` arrived with nothing said about where they came
126
+ from or what is known of them. `get_author` and `get_work` now state that they
127
+ are the BnF's own alignments, carried character for character, and that this
128
+ server requests data.bnf.fr alone and does not open them, so whether one still
129
+ answers is something it has not looked at. Re-spelling one, by decoding an
130
+ escape or adding an escape of this server's own, would produce an address the
131
+ catalogue never published.
132
+ - `find_digitised` published its per-role figures under `counts`, next to
133
+ `has_more`, where they read as a count of what the catalogue attaches. They
134
+ count the links in the answer, and Baudelaire's record returns a hundred and
135
+ one of them with more left out at the ceiling. The field is now
136
+ `links_returned_by_role`, it says what it counts, and a shortened answer says
137
+ that the figures fall short by however much was left out.
138
+ - A digitised link carried an address opening a small image beside an ARK naming
139
+ the document that image was taken from, with `url` described as the document.
140
+ Sixty-three of the hundred and one links on Baudelaire's record are such
141
+ addresses. Every link now carries `rendering`, the view the address asks
142
+ Gallica for, read off what follows the ARK name and null when the address
143
+ names the document; the answer counts the links carrying one.
144
+ - A record stating one text under `biographical_information` and under
145
+ `occupation` was returned as two fields, where it reads as two statements
146
+ agreeing. `get_author` now says when the two are identical character for
147
+ character.
148
+ - One identifier was refused with a different list of types depending on which
149
+ tool refused it, because a detail query gathers the classes of everything it
150
+ walks through. Every refusal now states the list the address itself answers
151
+ with, so the five tools describe one record the same way.
152
+ - `catalogue_url` came back null on `get_work` while `get_author` and
153
+ `list_editions` filled it, with nothing to read that silence by. Both fields
154
+ now say that the address is the one the record points at, that null is the
155
+ record stating none, and that no address is built in its place.
156
+ - The square brackets of cataloguing passed through `publisher` and `place`
157
+ unexplained, so `[S.l.]`, `[s.n.]` and a role such as `[éd., distrib.]` read as
158
+ what the item prints. The values are still passed on as published, and the
159
+ fields and a note now say what the brackets mean.
160
+ - The Gallica caveat was attached to answers from `get_author` and `get_work`
161
+ that carried no link, where it describes a list that is not there. It now
162
+ travels with the images only when they are returned.
163
+
5
164
  ## 1.0.1
6
165
 
7
166
  - `search_works` and `search_authors` answered a page sitting past the last row
package/README.md CHANGED
@@ -1,15 +1,20 @@
1
1
  # mcp-databnf
2
2
 
3
+ [![npm](https://img.shields.io/npm/v/mcp-databnf.svg)](https://www.npmjs.com/package/mcp-databnf)
4
+ [![CI](https://github.com/smeet666/mcp-databnf/actions/workflows/ci.yml/badge.svg)](https://github.com/smeet666/mcp-databnf/actions/workflows/ci.yml)
5
+ [![license](https://img.shields.io/npm/l/mcp-databnf.svg)](./LICENSE)
6
+ [![MCP Registry](https://img.shields.io/badge/MCP_Registry-listed-6E56CF)](https://registry.modelcontextprotocol.io/v0/servers?search=io.github.smeet666/mcp-databnf)
7
+ [![Glama](https://glama.ai/mcp/servers/smeet666/mcp-databnf/badges/score.svg)](https://glama.ai/mcp/servers/smeet666/mcp-databnf)
8
+ [![Install in Cursor](https://cursor.com/deeplink/mcp-install-dark.svg)](https://cursor.com/en/install-mcp?name=databnf&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsIm1jcC1kYXRhYm5mIl19)
9
+ [![Install in VS Code](https://img.shields.io/badge/VS_Code-Install-0098FF?style=flat&logo=visualstudiocode&logoColor=white)](https://insiders.vscode.dev/redirect/mcp/install?name=databnf&config=%7B%22name%22%3A%22databnf%22%2C%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22mcp-databnf%22%5D%7D)
10
+
3
11
  An MCP server for [data.bnf.fr](https://data.bnf.fr), the open catalogue of the
4
12
  Bibliothèque nationale de France. Look up an author, find a work, list the
5
- editions the BnF holds of it, and gather the links to what has been digitised.
13
+ editions the BnF holds of it, list what a person wrote, and gather the links to
14
+ what has been digitised.
6
15
 
7
16
  No API key. No account. Read-only.
8
17
 
9
- [![npm](https://img.shields.io/npm/v/mcp-databnf)](https://www.npmjs.com/package/mcp-databnf)
10
- [![CI](https://github.com/smeet666/mcp-databnf/actions/workflows/ci.yml/badge.svg)](https://github.com/smeet666/mcp-databnf/actions/workflows/ci.yml)
11
- [![licence: MIT](https://img.shields.io/badge/licence-MIT-green.svg)](LICENSE)
12
-
13
18
  ---
14
19
 
15
20
  ## What it is for
@@ -20,7 +25,7 @@ web search does not: which of two people bearing one name wrote a given book,
20
25
  what the BnF recorded as somebody's date and place of death, which editions of a
21
26
  work exist and who printed them, and which of those have been digitised.
22
27
 
23
- This server asks those questions for you, in six tools, and reports what the
28
+ This server asks those questions for you, in seven tools, and reports what the
24
29
  catalogue answers without adding to it.
25
30
 
26
31
  ## Install
@@ -42,7 +47,7 @@ npx mcp-databnf
42
47
  }
43
48
  ```
44
49
 
45
- ## The six tools
50
+ ## The seven tools
46
51
 
47
52
  | Tool | Answers |
48
53
  | ---------------- | ------------------------------------------------------------------------------------------------------------------------- |
@@ -50,11 +55,12 @@ npx mcp-databnf
50
55
  | `get_author` | Dates, places, occupation, language, country, Dewey class, and the same person in VIAF, IdRef, DBpedia, Wikidata and ISNI |
51
56
  | `search_works` | Which works have these words in their title |
52
57
  | `get_work` | Title, creators, date, language, form, subject, and whether the record is established or provisional |
58
+ | `list_works` | Which works the catalogue names a person the creator of, with the date and the form code it gives each |
53
59
  | `list_editions` | Publisher, place, year, edition statement, extent, ISBN, catalogue link, and the Gallica link when there is one |
54
60
  | `find_digitised` | Every digitised document the catalogue attaches to a person or a work, as links |
55
61
 
56
62
  A typical exchange asks `search_authors` for a name, reads the rows, and passes
57
- one identifier to `get_author` or `search_works`.
63
+ one identifier to `get_author`, to `list_works`, or on to `list_editions`.
58
64
 
59
65
  ## What it does not do, and why
60
66
 
@@ -83,13 +89,40 @@ match. This server says so rather than inventing an order the catalogue does not
83
89
  support, and it reports no total, because a total on a search that does not rank
84
90
  reads as a measure of relevance.
85
91
 
92
+ **A name is matched letter for letter, and only among the people.** The index
93
+ compares the characters given against the characters a cataloguer entered, so
94
+ `Dostoïevski`, `Dostoievski` and `Dostoevskij` are three searches reaching three
95
+ different sets of records, and a handful of rows under one spelling is no
96
+ evidence about the others. `search_authors` says so on every answer, and it reads
97
+ the person records alone: an organisation or a conference is outside what it
98
+ looked at, so an answer holding no row is never a statement that the authority
99
+ file holds no such heading.
100
+
101
+ **A list of works is one link, not a bibliography.** `list_works` walks
102
+ `dcterms:creator`, the statement that ties a work to the person who made it.
103
+ That statement is real and it is partial. The catalogue credits a person on a
104
+ record in other ways, and the BnF holds printed editions whose work it has never
105
+ established as a record of its own, so a title missing from the list is not a
106
+ title the person did not write. Every answer says so, and none of them reports a
107
+ total: a count of what one link reaches would be read as a count of what somebody
108
+ wrote.
109
+
110
+ **A form code says what a work declares, and an empty one says nothing.** A work
111
+ record points at a term of the BnF's work-form vocabulary, and this dataset
112
+ publishes no label for those terms. So `roman`, `poesi` and `te` arrive as the
113
+ codes they are: some read as words and some do not. The catalogue also states no
114
+ form at all on a great many works, and that silence is a form unstated rather
115
+ than a genre denied. Keeping the rows that carry one code therefore finds the
116
+ works declaring it and never all the works of that form, which is why the codes
117
+ are reported and no filter is offered on them.
118
+
86
119
  **It does not write biographies.** The field the BnF calls biographical
87
120
  information is an occupation on most records: Rimbaud's says _Poète_, and that is
88
121
  the whole of it. `get_author` returns that word and says what it is.
89
122
 
90
123
  **It exposes no raw SPARQL tool.** An arbitrary query is an unbounded load on a
91
124
  service a public institution pays for, and nothing here would control what the
92
- caller wrote. Every query this server sends is one of nine written in advance.
125
+ caller wrote. Every query this server sends is one of ten written in advance.
93
126
 
94
127
  ## The licence, and what it asks of you
95
128
 
@@ -205,8 +238,8 @@ retrieval.
205
238
 
206
239
  Un serveur MCP pour [data.bnf.fr](https://data.bnf.fr), le catalogue ouvert de la
207
240
  Bibliothèque nationale de France. Chercher un auteur, trouver une œuvre, lister
208
- les éditions que la BnF en conserve, et rassembler les liens vers ce qui a été
209
- numérisé.
241
+ les éditions que la BnF en conserve, lister ce qu'une personne a écrit, et
242
+ rassembler les liens vers ce qui a été numérisé.
210
243
 
211
244
  Sans clé d'API. Sans compte. En lecture seule.
212
245
 
@@ -219,7 +252,7 @@ tel livre, ce que la BnF a enregistré comme date et lieu de mort de quelqu'un,
219
252
  quelles éditions d'une œuvre existent et qui les a imprimées, et lesquelles ont
220
253
  été numérisées.
221
254
 
222
- Ce serveur pose ces questions pour vous, en six outils, et rapporte ce que le
255
+ Ce serveur pose ces questions pour vous, en sept outils, et rapporte ce que le
223
256
  catalogue répond sans y ajouter.
224
257
 
225
258
  ## Installation
@@ -239,16 +272,17 @@ npx mcp-databnf
239
272
  }
240
273
  ```
241
274
 
242
- ## Les six outils
275
+ ## Les sept outils
243
276
 
244
- | Outil | Répond à |
245
- | ---------------- | --------------------------------------------------------------------------------------------------------------------- |
246
- | `search_authors` | Qui la BnF enregistre sous ce nom, et quelle notice est laquelle |
247
- | `get_author` | Dates, lieux, profession, langue, pays, indice Dewey, et la même personne dans VIAF, IdRef, DBpedia, Wikidata et ISNI |
248
- | `search_works` | Quelles œuvres portent ces mots dans leur titre |
249
- | `get_work` | Titre, auteurs, date, langue, forme, sujet, et si la notice est établie ou provisoire |
250
- | `list_editions` | Éditeur, lieu, année, mention d'édition, pagination, ISBN, lien catalogue, et le lien Gallica quand il existe |
251
- | `find_digitised` | Tous les documents numérisés que le catalogue rattache à une personne ou à une œuvre, sous forme de liens |
277
+ | Outil | Répond à |
278
+ | ---------------- | ---------------------------------------------------------------------------------------------------------------------- |
279
+ | `search_authors` | Qui la BnF enregistre sous ce nom, et quelle notice est laquelle |
280
+ | `get_author` | Dates, lieux, profession, langue, pays, indice Dewey, et la même personne dans VIAF, IdRef, DBpedia, Wikidata et ISNI |
281
+ | `search_works` | Quelles œuvres portent ces mots dans leur titre |
282
+ | `get_work` | Titre, auteurs, date, langue, forme, sujet, et si la notice est établie ou provisoire |
283
+ | `list_works` | Quelles œuvres le catalogue attribue à une personne comme créatrice, avec la date et le code de forme qu'il leur donne |
284
+ | `list_editions` | Éditeur, lieu, année, mention d'édition, pagination, ISBN, lien catalogue, et le lien Gallica quand il existe |
285
+ | `find_digitised` | Tous les documents numérisés que le catalogue rattache à une personne ou à une œuvre, sous forme de liens |
252
286
 
253
287
  ## Ce qu'il ne fait pas, et pourquoi
254
288
 
@@ -278,6 +312,34 @@ Ce serveur le dit, plutôt que d'inventer un ordre que le catalogue ne porte pas
278
312
  et il ne rapporte aucun total : sur une recherche qui ne classe pas, un total se
279
313
  lit comme une mesure de pertinence.
280
314
 
315
+ **Un nom est apparié lettre à lettre, et seulement parmi les personnes.**
316
+ L'index compare les caractères donnés à ceux qu'un catalogueur a saisis :
317
+ `Dostoïevski`, `Dostoievski` et `Dostoevskij` sont donc trois recherches qui
318
+ atteignent trois ensembles de notices différents, et quelques lignes obtenues
319
+ sous une graphie ne disent rien des autres. `search_authors` l'énonce sur chaque
320
+ réponse, et il ne lit que les notices de personnes : un organisme ou un congrès
321
+ est hors de ce qu'il a regardé, si bien qu'une réponse sans ligne n'affirme
322
+ jamais que le fichier d'autorité ne tient pas une telle vedette.
323
+
324
+ **Une liste d'œuvres est un lien, pas une bibliographie.** `list_works` suit
325
+ `dcterms:creator`, l'énoncé qui rattache une œuvre à qui l'a faite. Cet énoncé
326
+ est réel et il est partiel : le catalogue crédite une personne sur une notice par
327
+ d'autres voies, et la BnF conserve des éditions imprimées dont l'œuvre n'a jamais
328
+ été établie comme notice à part entière. Un titre absent de la liste n'est donc
329
+ pas un titre que la personne n'a pas écrit. Chaque réponse le dit, et aucune ne
330
+ rapporte de total : un compte de ce qu'un seul lien atteint se lirait comme un
331
+ compte de ce que quelqu'un a écrit.
332
+
333
+ **Un code de forme dit ce qu'une œuvre déclare ; son absence ne dit rien.** Une
334
+ notice d'œuvre pointe vers un terme du vocabulaire des formes d'œuvre de la BnF,
335
+ et ce jeu de données n'en publie aucun libellé. `roman`, `poesi` et `te`
336
+ arrivent donc tels quels : certains se lisent comme des mots, d'autres non. Le
337
+ catalogue n'énonce par ailleurs aucune forme sur quantité d'œuvres, et ce
338
+ silence est une forme non déclarée, pas un genre exclu. Garder les lignes qui
339
+ portent un code trouve donc les œuvres qui le déclarent, jamais toutes les
340
+ œuvres de cette forme : les codes sont rapportés, et aucun filtre n'est offert
341
+ dessus.
342
+
281
343
  **Il n'écrit pas de biographies.** Le champ que la BnF appelle information
282
344
  biographique contient une profession sur la plupart des notices : celle de
283
345
  Rimbaud dit _Poète_, et c'est tout. `get_author` rend ce mot et dit ce que c'est.
@@ -285,7 +347,7 @@ Rimbaud dit _Poète_, et c'est tout. `get_author` rend ce mot et dit ce que c'es
285
347
  **Il n'expose aucun outil SPARQL brut.** Une requête arbitraire est une charge
286
348
  non bornée sur un service qu'une institution publique paie, et rien ici ne
287
349
  contrôlerait ce que l'appelant a écrit. Chacune des requêtes envoyées est l'une
288
- des neuf écrites à l'avance.
350
+ des dix écrites à l'avance.
289
351
 
290
352
  ## La licence, et ce qu'elle vous demande
291
353
 
@@ -36,6 +36,14 @@ interface DigitisedLink {
36
36
  ark: string;
37
37
  /** The address to open, exactly as data.bnf.fr publishes it. */
38
38
  url: string;
39
+ /**
40
+ * The view the address asks Gallica for, such as `thumbnail` or `f3.item`.
41
+ *
42
+ * Null when the address names the document itself. An address asking for a
43
+ * view opens that view, so the ARK beside it names the document a rendering
44
+ * was taken from rather than what a reader will see.
45
+ */
46
+ rendering: string | null;
39
47
  /**
40
48
  * Which statement attached this link to the record.
41
49
  *
@@ -130,6 +138,24 @@ interface WorkSummary {
130
138
  status: "established" | "provisional";
131
139
  sourceUrl: string;
132
140
  }
141
+ /** One row of the works a person is named the creator of. */
142
+ interface AuthoredWork {
143
+ id: string;
144
+ title: string | null;
145
+ /** The date the record gives the work, as published. */
146
+ date: string | null;
147
+ /** The same date as a number, when the record states one. */
148
+ year: number | null;
149
+ /**
150
+ * Terms of the BnF work-form vocabulary the record points at, as the terms
151
+ * themselves. The vocabulary publishes no label for them in this dataset, so
152
+ * nothing here translates one: some read as words and some do not. An empty
153
+ * list is a form the record does not state.
154
+ */
155
+ forms: string[];
156
+ status: "established" | "provisional";
157
+ sourceUrl: string;
158
+ }
133
159
  /** One work, read in full. */
134
160
  interface WorkDetail {
135
161
  id: string;
@@ -203,8 +229,32 @@ interface Page<T> {
203
229
  hasMore: boolean;
204
230
  /** Rows the endpoint sent that could not be read, present only when some were. */
205
231
  skipped?: number;
232
+ /**
233
+ * Whether the fixed window a text search reads off the index came back full.
234
+ *
235
+ * A full window bounds the answer: records carrying the words sit past it and
236
+ * were never read, so `hasMore` false means the rows of the window stop here
237
+ * rather than the catalogue holding nothing further. A window with room to
238
+ * spare holds everything that matched, which makes the answer both complete
239
+ * and repeatable.
240
+ *
241
+ * Null when the endpoint sent no occupancy, so nothing is claimed either way.
242
+ * Absent on a listing that reads no index window.
243
+ */
244
+ indexWindowFull?: boolean | null;
206
245
  }
207
246
 
247
+ /** How a piece of caller text became the terms the index was given. */
248
+ interface SearchReading {
249
+ /** The words the text was cut into, each one quoted into the query. */
250
+ words: string[];
251
+ /** The terms the index requires, after it splits inside a word. */
252
+ terms: string[];
253
+ /** The characters that marked nothing and were removed before the cutting. */
254
+ invisible: string[];
255
+ /** The visible characters that reached no term. */
256
+ setAside: string[];
257
+ }
208
258
  type EntityKind = "ark" | "temp-work";
209
259
  interface EntityId {
210
260
  kind: EntityKind;
@@ -281,6 +331,21 @@ declare class BnfClient {
281
331
  identify(input: string): EntityId;
282
332
  /** The words a search will actually look for, which a tool reports back. */
283
333
  words(input: string): string[];
334
+ /**
335
+ * The terms the index requires, which is what a row's presence rests on.
336
+ *
337
+ * The index splits inside a word at an apostrophe and at a hyphen, so a word
338
+ * a caller wrote as one can reach it as two, each required on its own.
339
+ */
340
+ indexTerms(words: readonly string[]): string[];
341
+ /**
342
+ * How a piece of caller text became the terms the search was run with.
343
+ *
344
+ * A tool reports the reading back, so the same reading has to be the one the
345
+ * query was built from: the words, the terms the index makes of them, and
346
+ * the characters that reached neither.
347
+ */
348
+ searchReading(input: string): SearchReading;
284
349
  searchAuthors(name: string, limit: number, offset: number): Promise<Read<Page<AuthorSummary>>>;
285
350
  getAuthor(id: EntityId): Promise<Read<AuthorDetail>>;
286
351
  /**
@@ -293,6 +358,14 @@ declare class BnfClient {
293
358
  private refuse;
294
359
  searchWorks(title: string, limit: number, offset: number): Promise<Read<Page<WorkSummary>>>;
295
360
  getWork(id: EntityId): Promise<Read<WorkDetail>>;
361
+ /**
362
+ * The works one person is named the creator of.
363
+ *
364
+ * The record is not checked here. A person the catalogue credits with nothing
365
+ * and an address that names no person both answer with no rows, and telling
366
+ * them apart costs a second query that only an empty answer needs.
367
+ */
368
+ worksByAuthor(id: EntityId, limit: number, offset: number): Promise<Read<Page<AuthoredWork>>>;
296
369
  listEditions(id: EntityId, limit: number, offset: number): Promise<Read<Page<Edition>>>;
297
370
  /** Digitised documents attached to a work and to its editions. */
298
371
  digitisedForWork(id: EntityId, limit: number): Promise<Read<Page<DigitisedLink>>>;