📄 README.md
D-OPEN SOVEREIGN
1# `@the_library/standard-adapters`
2
3Runtime `StandardAdapter` implementations for every international identifier and classification standard defined in the companion package [`@the_library/canonical-schemas`](../canonical-schemas). This package ships **executable code** — normalization, validation, check-digit algorithms, and external API lookups. `canonical-schemas` ships the JSON governance data and TypeScript interfaces those implementations satisfy.
4
5> If `canonical-schemas` answers "what is an ISBN and what props does it contribute?", `standard-adapters` answers "given a string, is it a valid ISBN, and how do I fetch metadata for it?"
6
7---
8
9## Licence
10
11Published under the **D-Open Code Sovereign Licence v1.0**, the same licence covering `canonical-schemas` and the rest of the D-SAFE engine. See [`canonical-schemas/README.md`](../canonical-schemas/README.md#the-d-open-code-sovereign-licence--source-code) for the full licence architecture.
12
13---
14
15## Package layout
16
17```
18packages/standard-adapters/
19 src/
20 index.ts Barrel export — all 11 adapters + apiKeyDictionary
21 api-keys.ts apiKeyDictionary singleton (set/get/require)
22
23 isbn/index.ts ISBNAdapter — Open Library + Google Books APIs
24 isbn/types.ts OpenLibraryBookResult, GoogleBooksVolumeResult
25 isbn/isbn.test.ts
26
27 issn/index.ts ISSNAdapter
28 isni/index.ts ISNIAdapter — ISNI SRU API
29 orcid/index.ts ORCIDAdapter — ORCID Public Record + Search APIs
30
31 ddc/index.ts DDCAdapter — classification, no external API
32 lcc/index.ts LCCAdapter — classification, no external API
33 udc/index.ts UDCAdapter — classification, no external API
34 clc/index.ts CLCAdapter — classification, no external API
35 bbk/index.ts BBKAdapter — classification, no external API
36 tlc/index.ts TLCAdapter — classification, no external API
37 cc/index.ts CCAdapter — classification, no external API
38
39 tsup.config.ts Per-adapter entry points → subpath exports
40 vitest.config.ts include: src/**/*.test.ts
41```
42
43Each identifier standard (`isbn`, `issn`, `isni`, `orcid`) also has a `types.ts` declaring the shape of its external API responses, and a `*.test.ts` covering `normalize` / `validate` / `toLinkedDataUri` plus an `INTEGRATION`-gated live-API test (see [Testing](#testing) below). Classification standards (`ddc`, `lcc`, `udc`, `clc`, `bbk`, `tlc`, `cc`) have no external lookup — a classifier assigns the notation manually — so they carry no `types.ts` and an empty `apis: {}`.
44
45---
46
47## The `StandardAdapter` contract
48
49Every export in this package implements the interface defined in `@the_library/canonical-schemas/types/StandardAdapter`:
50
51```typescript
52export interface StandardAdapter<TProps, TApis> {
53 readonly standardId: string;
54 readonly uriScheme: string | null;
55 readonly apis: TApis;
56 normalize(value: string): string;
57 display(value: string): string;
58 validate(value: string | null | undefined): ValidationResult;
59 toLinkedDataUri(value: string): string;
60}
61```
62
63- **`normalize`** — canonicalizes input into the standard's authoritative storage form (e.g. ISBN-10 → ISBN-13, hyphens stripped). This is the form persisted on-chain.
64- **`display`** — re-hyphenates/formats the normalized value for UI presentation (e.g. `978-0-262-03384-8`).
65- **`validate`** — runs pattern matching and, where applicable, the standard's check-digit algorithm (`isbn-13-luhn`, `iso7064-mod11-2`). Returns `{ valid, error? }` rather than throwing, so callers can surface the reason inline.
66- **`toLinkedDataUri`** — produces the RDF/LOD-compatible URI (`urn:isbn:...`, `https://orcid.org/...`, `https://id.loc.gov/authorities/classification/...`) used for external linked-data output.
67- **`apis`** — a map of `StandardApiAdapter<TProps, TResult>` objects (`{ apiId, name, fetch(props) }`) for standards with external lookups. Empty (`{}`) for classification standards.
68
69Governance policy (see `canonical-schemas/governance/GOVERNANCE.md`) requires each adapter's `apis` keys to match the `externalApis[].id` entries declared in the corresponding `canonical-schemas/standards/<id>.json` file 1:1. **Note:** as of this writing, `packages/db_schemas/src/compiler.ts` enforces standard existence, `uriScheme` presence, and prop-name collisions for `uidStrategy`/`instanceUidStrategy` — it does not yet cross-check `externalApis[].id` against an adapter's `apis` object keys at build time. Keeping the two in sync is currently a code-review convention, not a compiler-enforced invariant.
70
71### Example: `ISBNAdapter`
72
73```typescript
74import { ISBNAdapter } from '@the_library/standard-adapters/isbn';
75
76ISBNAdapter.normalize('0-262-03384-4'); // '9780262033848'
77ISBNAdapter.display('9780262033848'); // '978-0-262-03384-8'
78ISBNAdapter.validate('978-0-262-03384-8'); // { valid: true }
79ISBNAdapter.toLinkedDataUri('9780262033848'); // 'urn:isbn:9780262033848'
80
81const meta = await ISBNAdapter.apis.openlibrary.fetch({ isbn: '9780262033848' });
82// { title, authors, publishDate, pageCount, coverUrl, description }
83```
84
85### Example: a classification adapter (no external API)
86
87```typescript
88import { DDCAdapter } from '@the_library/standard-adapters/ddc';
89
90DDCAdapter.validate('813.54'); // { valid: true }
91DDCAdapter.apis; // {}
92```
93
94---
95
96## `apiKeyDictionary`
97
98Adapters that call an API requiring credentials (`googlebooks` today; `requiresApiKey: true` in the standard's `externalApis[]` entry) read from a package-local singleton rather than accepting keys as arguments:
99
100```typescript
101import { apiKeyDictionary } from '@the_library/standard-adapters';
102
103apiKeyDictionary.set('googlebooks', import.meta.env.VITE_GOOGLE_BOOKS_API_KEY);
104```
105
106- `set(apiId, value)` — throws if `value` is empty.
107- `get(apiId)` — returns `undefined` if unset.
108- `require(apiId)` — used internally by adapters; throws immediately with a message naming the missing key if not configured. Fail fast at configuration time, not mid-request.
109
110This singleton, and all network calls, live entirely in this package. `canonical-schemas` has no knowledge of it and no runtime dependency on `fetch`.
111
112---
113
114## Standards implemented
115
116| Adapter | Standard ID | External APIs | Check digit |
117|---|---|---|---|
118| `ISBNAdapter` | `isbn` | Open Library, Google Books (`requiresApiKey`) | `isbn-13-luhn` |
119| `ISSNAdapter` | `issn` | — | `iso7064-mod11-2` |
120| `ISNIAdapter` | `isni` | ISNI SRU | `iso7064-mod11-2` |
121| `ORCIDAdapter` | `orcid` | ORCID Public Record, ORCID Public Search | `iso7064-mod11-2` |
122| `DDCAdapter` | `ddc` | — | — |
123| `LCCAdapter` | `lcc` | — | — |
124| `UDCAdapter` | `udc` | — | — |
125| `CLCAdapter` | `clc` | — | — |
126| `BBKAdapter` | `bbk` | — | — |
127| `TLCAdapter` | `tlc` | — | — |
128| `CCAdapter` | `cc` | — | — |
129
130Full governance metadata (governing body, ISO reference, revision cycle, field mappings) for each of these lives in the matching `canonical-schemas/standards/<id>.json` file, not here — this package only implements behavior, it doesn't restate governance facts.
131
132---
133
134## Consumers
135
136```
137@the_library/standard-adapters
138 │
139 ▼
140packages/public/src/models/ (models_v2 schema generator + generated model classes)
141 │ imports adapters by subpath, e.g. "@the_library/standard-adapters/isbn"
142 │ wires them into generated fetchFrom{ApiId}() / searchFrom{ApiId}() methods
143 │
144 ▼
145apps/vue.datapond.earth, apps/book-manager
146```
147
148The `models_v2` codegen (`packages/public/src/models/schemaGenerator.ts`) reads each canonical model's `uidStrategy` / `instanceUidStrategy`, imports the matching adapter via its **subpath export** (`@the_library/standard-adapters/isbn`, not the root barrel), and generates typed fetch/search methods on the model class from the adapter's `TApis` map.
149
150Consumers should generally import by subpath (`@the_library/standard-adapters/ddc`) rather than the root `@the_library/standard-adapters` barrel, so bundlers only pull in the adapters actually referenced.
151
152---
153
154## Build
155
156`tsup.config.ts` declares one entry point per adapter plus the root barrel, producing per-adapter ESM output and matching `.d.ts` files. `package.json`'s `exports` map exposes each as a subpath (`./isbn`, `./issn`, `./isni`, `./orcid`, `./ddc`, `./lcc`, `./udc`, `./clc`, `./bbk`, `./tlc`, `./cc`) so a consumer that only needs `DDCAdapter` doesn't bundle the `fetch`-calling identifier adapters.
157
158```sh
159pnpm run build # tsup — builds dist/, one output per adapter
160```
161
162---
163
164## Testing
165
166```sh
167pnpm test # vitest run — normalize/validate/toLinkedDataUri, no network
168pnpm run test:integration # INTEGRATION=true vitest run — also hits live external APIs
169```
170
171Each identifier adapter's test file gates its live-API assertions behind `describe.skipIf(!process.env.INTEGRATION)`, so ordinary `pnpm test` runs are fast, deterministic, and offline — check-digit and normalization logic is covered without any network dependency. Only `test:integration` actually calls Open Library, Google Books, ISNI SRU, and ORCID.
172
173---
174
175## Adding a new standard
176
177A new standard always requires **two coordinated PRs**, reviewed and merged together per governance policy (see the compiler caveat above — this is currently enforced by review, not by the build):
178
1791. **`canonical-schemas`** — add `standards/<id>.json` (`StandardDefinition`), including `externalApis[]` if applicable.
1802. **`standard-adapters`** (this package) — add `src/<id>/index.ts` implementing `StandardAdapter`, matching `apis` keys 1:1 with the JSON's `externalApis[].id` entries; add the entry point to `tsup.config.ts` and the subpath to `package.json#exports`; export it from `src/index.ts`.
181
182See [`canonical-schemas/governance/GOVERNANCE.md`](../canonical-schemas/governance/GOVERNANCE.md) for the review and versioning rules — a new standard is a coordinated **minor** bump on both packages.
183
184---
185
186## Related packages
187
188| Package | Role |
189|---|---|
190| [`@the_library/canonical-schemas`](../canonical-schemas) | Governance JSON + `StandardAdapter`/`StandardDefinition` TypeScript interfaces this package implements |
191| [`packages/db_schemas`](../db_schemas) | ORM compiler (internally still logged as `db_schema_v2`); enforces `uidStrategy` standard existence, `uriScheme`, and prop collisions — does not yet cross-check `apis` keys against `externalApis[]` |
192| [`packages/public`](../public) | `models_v2` schema generator; imports adapters by subpath into generated model classes |
193