@lde/iiif-validator
Validates that a URL dereferences to a valid IIIF Presentation Manifest. A small building block for Linked Data tooling that needs to tell a declared IIIF manifest apart from one that actually resolves, parses, and loads in a real viewer.
Installation
sh
npm install @lde/iiif-validatorUsage
ts
import { validateManifest } from '@lde/iiif-validator';
const verdict = await validateManifest('https://example.org/manifest.json');
if (verdict.valid) {
// verdict.reason === 'valid-manifest'
}validateManifest never throws; every outcome is reported as a ManifestValidation:
ts
interface ManifestValidation {
valid: boolean;
reason:
| 'valid-manifest'
| 'timeout'
| 'network-error'
| 'http-error'
| 'invalid-json'
| 'binary-content'
| 'not-a-manifest'
| 'does-not-load';
}Behaviour
- Dereference over HTTP with
Accept: */*, following redirects, using the globalfetchwith anAbortSignaltimeout (default 10 000 ms). The wildcard mirrors what real IIIF viewers send (the browserfetchdefault); a JSON-specificAcceptwould be more correct but trips up hosts that do backwards content negotiation – serving the manifest to*/*while returning 404 for a JSON-specific request. BothfetchandtimeoutMsare injectable via the options argument. - Binary media is rejected without downloading. A response whose
Content-Typeannounces a binary media asset –image/*,audio/*, orvideo/*– yieldsbinary-contentand the body is never read: the stream is cancelled, so aschema:contentUrlthat dereferences to a full-resolution image or video does not get buffered just to fail JSON parsing. A missing or ambiguous type (text/plain,application/octet-stream) is not treated as binary, so a manifest served with an odd type is still parsed. - Version-aware structural check. A document is manifest-shaped when the response is HTTP 2xx, the body parses as JSON, its
@contextreferences an IIIF Presentation context, and itstype/@typeindicates a manifest – accepting both v3 (Manifest) and v2 (sc:Manifest). The@contextvalue may be a string, an array, or an object; all forms are handled. The version segment of the context is accepted version-agnostically. - Viewer-load gate. Being manifest-shaped is not enough: a document can pass every structural check yet fail to load in the dominant Vault/
@iiif/parser-based viewers (Mirador 4, Clover, Theseus), which eagerly upgrade every manifest to Presentation 3 and normalise the whole tree on load. A single structural slip – e.g. anullwhere anAnnotationPagebelongs – crashes that pass, so the manifest renders in no such viewer. The validator reproduces that load path with@iiif/parser(upgrade()thennormalize()); if it throws, the verdict isdoes-not-load.upgrade()is a no-op for v3, so this one path covers both v2 and v3. There are only two tiers – valid or invalid; cosmetic deviations that still load (a non-canonicalrightsURI,image/jpginstead ofimage/jpeg) stay valid. - Strict failure semantics, no retries. A timeout, network error, non-2xx status, unparseable body, binary media type, missing IIIF
@context, wrongtype, or a document that does not load all yieldvalid: falsewith the corresponding coarsereason. There is no deep JSON Schema validation and no dependency on the hosted IIIF Presentation Validator service.
Options
ts
interface ValidateManifestOptions {
/** `fetch` implementation to use. Injectable for testing; defaults to the global `fetch`. */
fetch?: typeof globalThis.fetch;
/** Per-request timeout in milliseconds. Defaults to 10 000. */
timeoutMs?: number;
}