@prose-reader/metadata-fetcher 1.340.0 → 1.342.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/fetchMetadata.d.ts +15 -25
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +9 -7
- package/dist/index.js +210 -231
- package/dist/index.js.map +1 -1
- package/dist/match/scoreMetadataCandidate.d.ts +5 -6
- package/dist/metadataInputFromResolvedArchive.d.ts +9 -0
- package/dist/providers/projectGutenberg/identifier.d.ts +3 -6
- package/dist/types/fetchMetadataInput.d.ts +30 -0
- package/dist/types/fetchedMetadata.d.ts +7 -21
- package/dist/types/match.d.ts +3 -4
- package/dist/types/provider.d.ts +10 -14
- package/dist/utils/hasSearchTerms.d.ts +2 -2
- package/package.json +4 -4
- package/dist/merge/mergeResolvedMetadata.d.ts +0 -21
- /package/dist/{merge/mergeResolvedMetadata.test.d.ts → metadataInputFromResolvedArchive.test.d.ts} +0 -0
|
@@ -1,10 +1,7 @@
|
|
|
1
|
-
import { ResolvedMetadata } from '@prose-reader/archive-reader';
|
|
2
1
|
import { MetadataMatch } from './match.ts';
|
|
3
2
|
/**
|
|
4
|
-
* What one provider had to say
|
|
5
|
-
*
|
|
6
|
-
* merged, in {@link FetchedMetadata.metadata}, so a wrong precedence opinion
|
|
7
|
-
* stays revisable because the per-provider values never left the entity.
|
|
3
|
+
* What one provider had to say: every candidate, with the provider identity
|
|
4
|
+
* needed to present or revisit those results independently.
|
|
8
5
|
*/
|
|
9
6
|
export type FetchedMetadataSource = {
|
|
10
7
|
readonly provider: {
|
|
@@ -38,8 +35,9 @@ export type FailedMetadataProvider = {
|
|
|
38
35
|
readonly status?: number;
|
|
39
36
|
};
|
|
40
37
|
/**
|
|
41
|
-
* What remote catalogs
|
|
42
|
-
*
|
|
38
|
+
* What remote catalogs found for a book, kept as ranked alternatives rather
|
|
39
|
+
* than consolidated into a potentially synthetic record. The entity is plain
|
|
40
|
+
* JSON: structured-clone-able, persistable and cacheable.
|
|
43
41
|
*/
|
|
44
42
|
export type FetchedMetadata = {
|
|
45
43
|
/**
|
|
@@ -47,19 +45,6 @@ export type FetchedMetadata = {
|
|
|
47
45
|
* shape or meaning of existing fields changes incompatibly.
|
|
48
46
|
*/
|
|
49
47
|
readonly version: number;
|
|
50
|
-
/**
|
|
51
|
-
* What the providers found, merged field-wise with the highest-scoring
|
|
52
|
-
* accepted match winning. Empty when nothing matched confidently enough.
|
|
53
|
-
*
|
|
54
|
-
* **Only** the remote answer: the local metadata is deliberately not folded
|
|
55
|
-
* in, so a consumer decides who wins.
|
|
56
|
-
*
|
|
57
|
-
* ```ts
|
|
58
|
-
* // trust the book over the catalog, fill the gaps from the catalog
|
|
59
|
-
* const metadata = mergeResolvedMetadata(resolved.metadata, fetched.metadata)
|
|
60
|
-
* ```
|
|
61
|
-
*/
|
|
62
|
-
readonly metadata: ResolvedMetadata;
|
|
63
48
|
/**
|
|
64
49
|
* Every match across every provider, ranked best-first — the list a "did you
|
|
65
50
|
* mean?" picker renders. Ties keep the order the providers were declared in,
|
|
@@ -73,7 +58,8 @@ export type FetchedMetadata = {
|
|
|
73
58
|
* leaves — a consumer refusing to cache a partial answer needs it.
|
|
74
59
|
*
|
|
75
60
|
* ```ts
|
|
76
|
-
* const
|
|
61
|
+
* const input = metadataInputFromResolvedArchive(resolved)
|
|
62
|
+
* const fetched = await fetchMetadata(input, { providers })
|
|
77
63
|
*
|
|
78
64
|
* // don't persist "we found nothing" when we simply couldn't ask
|
|
79
65
|
* if (fetched.failedProviders.length === 0) cache.set(bookId, fetched)
|
package/dist/types/match.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { ResolvedMetadata } from '@prose-reader/archive-reader';
|
|
|
3
3
|
* The fields the matcher compares — only ever when **both** sides state one,
|
|
4
4
|
* so what the query doesn't know cannot count against a candidate.
|
|
5
5
|
*/
|
|
6
|
-
export type MetadataMatchField = "isbn" | "gtin" | "identifiers" | "title" | "
|
|
6
|
+
export type MetadataMatchField = "isbn" | "gtin" | "identifiers" | "title" | "authors" | "series" | "publisher" | "publishedYear" | "languages" | "numberOfPages";
|
|
7
7
|
/**
|
|
8
8
|
* One field comparison. The compared values sit next to the score so a match
|
|
9
9
|
* is explainable to a user — "same title, different edition publisher" —
|
|
@@ -36,9 +36,8 @@ export type MetadataMatch = {
|
|
|
36
36
|
readonly score: number;
|
|
37
37
|
readonly signals: ReadonlyArray<MetadataMatchSignal>;
|
|
38
38
|
/**
|
|
39
|
-
* Reached `minScore
|
|
40
|
-
*
|
|
41
|
-
* consumer to offer for manual confirmation.
|
|
39
|
+
* Reached `minScore`. Rejected matches are kept and ranked, for a consumer
|
|
40
|
+
* to offer for manual confirmation.
|
|
42
41
|
*/
|
|
43
42
|
readonly accepted: boolean;
|
|
44
43
|
readonly metadata: ResolvedMetadata;
|
package/dist/types/provider.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ResolvedMetadata } from '@prose-reader/archive-reader';
|
|
2
|
-
|
|
2
|
+
import { FetchMetadataInput } from './fetchMetadataInput.ts';
|
|
3
3
|
export type MetadataProviderContext = {
|
|
4
4
|
/**
|
|
5
5
|
* How many candidates the caller will keep. A hint for the provider's own
|
|
@@ -27,28 +27,24 @@ export type MetadataCandidate = {
|
|
|
27
27
|
};
|
|
28
28
|
/**
|
|
29
29
|
* A pluggable metadata source. Implementing one is: pick a stable `id`, read
|
|
30
|
-
* the terms you can search on out of the
|
|
30
|
+
* the terms you can search on out of the compact input, return normalized
|
|
31
31
|
* candidates.
|
|
32
32
|
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
* shape to learn: the vocabulary is already sparse by contract (`field !==
|
|
36
|
-
* undefined` is a reliable presence check) and already carries everything a
|
|
37
|
-
* catalog could key on, down to the format-scoped corners. `metadataAuthors`
|
|
38
|
-
* is exported for the one derivation that needs the role vocabulary.
|
|
33
|
+
* Inputs contain only fields understood for lookup and matching. Returned
|
|
34
|
+
* candidates remain rich {@link ResolvedMetadata} entities.
|
|
39
35
|
*
|
|
40
36
|
* ```ts
|
|
41
|
-
* import { type MetadataProvider
|
|
37
|
+
* import { type MetadataProvider } from "@prose-reader/metadata-fetcher"
|
|
42
38
|
*
|
|
43
39
|
* const myProvider: MetadataProvider = {
|
|
44
40
|
* id: "myCatalog",
|
|
45
41
|
* name: "My Catalog",
|
|
46
|
-
* search: async (
|
|
47
|
-
* if (
|
|
42
|
+
* search: async (input, { limit, signal }) => {
|
|
43
|
+
* if (input.title === undefined) return []
|
|
48
44
|
*
|
|
49
45
|
* const response = await fetch(
|
|
50
|
-
* `https://example.com/search?q=${encodeURIComponent(
|
|
51
|
-
* `&author=${encodeURIComponent(
|
|
46
|
+
* `https://example.com/search?q=${encodeURIComponent(input.title)}` +
|
|
47
|
+
* `&author=${encodeURIComponent(input.authors?.[0] ?? "")}`,
|
|
52
48
|
* { signal },
|
|
53
49
|
* )
|
|
54
50
|
* const { results } = await response.json()
|
|
@@ -72,5 +68,5 @@ export type MetadataProvider = {
|
|
|
72
68
|
* nothing to search on, or nothing found. Throwing is allowed — a failing
|
|
73
69
|
* provider never fails the fetch, it lands in `failedProviders`.
|
|
74
70
|
*/
|
|
75
|
-
readonly search: (
|
|
71
|
+
readonly search: (input: FetchMetadataInput, context: MetadataProviderContext) => Promise<ReadonlyArray<MetadataCandidate>>;
|
|
76
72
|
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { FetchMetadataInput } from '../types/fetchMetadataInput.ts';
|
|
2
2
|
/**
|
|
3
3
|
* Whether a catalog has anything to go on, so a lookup with nothing to ask
|
|
4
4
|
* about costs no round trip. Publication details, a language, or a page count
|
|
5
5
|
* narrow a search but cannot start one.
|
|
6
6
|
*/
|
|
7
|
-
export declare const hasSearchTerms: (
|
|
7
|
+
export declare const hasSearchTerms: (input: FetchMetadataInput) => boolean;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@prose-reader/metadata-fetcher",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.342.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.cjs",
|
|
6
6
|
"module": "./dist/index.js",
|
|
@@ -29,9 +29,9 @@
|
|
|
29
29
|
"test:watch": "vitest watch"
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@prose-reader/archive-reader": "^1.
|
|
33
|
-
"@prose-reader/shared": "^1.
|
|
32
|
+
"@prose-reader/archive-reader": "^1.342.0",
|
|
33
|
+
"@prose-reader/shared": "^1.342.0",
|
|
34
34
|
"xmldoc": "^2.0.0"
|
|
35
35
|
},
|
|
36
|
-
"gitHead": "
|
|
36
|
+
"gitHead": "99cc96dab3e29677ab017246bbad836b54720284"
|
|
37
37
|
}
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import { ResolvedMetadata } from '@prose-reader/archive-reader';
|
|
2
|
-
/**
|
|
3
|
-
* Merges several {@link ResolvedMetadata} into one, **first defined wins**.
|
|
4
|
-
* Precedence is the caller's — pass the sources in the order you trust them.
|
|
5
|
-
*
|
|
6
|
-
* ```ts
|
|
7
|
-
* // the book over the catalogs, catalogs filling the gaps
|
|
8
|
-
* const metadata = mergeResolvedMetadata(resolved.metadata, fetched.metadata)
|
|
9
|
-
* ```
|
|
10
|
-
*
|
|
11
|
-
* Field-wise rather than object-wise, so a source knowing only a cover
|
|
12
|
-
* contributes it without hiding another's title. Two fields are additive
|
|
13
|
-
* instead: `identifiers` concatenate (deduped on scheme + value, mirroring
|
|
14
|
-
* `resolveMetadata`), and `belongsTo` merges `series` and `collection`
|
|
15
|
-
* independently.
|
|
16
|
-
*
|
|
17
|
-
* Everything else takes the first stated value whole. Unioning keyword lists
|
|
18
|
-
* across catalogs is a judgement call for the consumer, not for a merge that
|
|
19
|
-
* has to stay predictable.
|
|
20
|
-
*/
|
|
21
|
-
export declare const mergeResolvedMetadata: (...entries: ReadonlyArray<ResolvedMetadata | undefined>) => ResolvedMetadata;
|
/package/dist/{merge/mergeResolvedMetadata.test.d.ts → metadataInputFromResolvedArchive.test.d.ts}
RENAMED
|
File without changes
|