@ndes/iiif-images 0.0.0-stage → 0.1.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/README.md +126 -2
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1 -0
- package/dist/lib/iiif-images.d.ts +52 -0
- package/dist/lib/iiif-images.d.ts.map +1 -0
- package/dist/lib/iiif-images.js +337 -0
- package/dist/schema.d.ts +169 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +85 -0
- package/package.json +54 -3
package/README.md
CHANGED
|
@@ -1,3 +1,127 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @ndes/iiif-images
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Enriches SCHEMA-AP-NDE works with the dimensions and sizes of the IIIF image that
|
|
4
|
+
represents them, so a frontend can reserve the right aspect ratio and pick a size
|
|
5
|
+
before the image loads.
|
|
6
|
+
|
|
7
|
+
It is a quad transform for `@lde/search-indexer`. For each work with a IIIF
|
|
8
|
+
Presentation manifest in `associatedMedia`, it reads the manifest, selects an image,
|
|
9
|
+
reads its image service’s `info.json` if it has one and mints a nested
|
|
10
|
+
`representativeImage`.
|
|
11
|
+
|
|
12
|
+
## Usage
|
|
13
|
+
|
|
14
|
+
Declare the fields in your schema module. The `/schema` entry point has no runtime
|
|
15
|
+
dependencies, so a bundled schema module stays self-contained:
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
import {
|
|
19
|
+
iiifImageTypes,
|
|
20
|
+
representativeImageField,
|
|
21
|
+
} from '@ndes/iiif-images/schema';
|
|
22
|
+
|
|
23
|
+
const creativeWork = defineSearchType({
|
|
24
|
+
name: 'CreativeWork',
|
|
25
|
+
class: 'https://schema.org/CreativeWork',
|
|
26
|
+
fields: [representativeImageField /* … */],
|
|
27
|
+
});
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Add `iiifImageTypes` to the schema, then attach the transform after any transform
|
|
31
|
+
that repairs IRIs:
|
|
32
|
+
|
|
33
|
+
```ts
|
|
34
|
+
import { iiifImages } from '@ndes/iiif-images';
|
|
35
|
+
|
|
36
|
+
const images = iiifImages(schema, { storePath: '/provenance/iiif.sqlite' });
|
|
37
|
+
await images.check();
|
|
38
|
+
|
|
39
|
+
const transform = images.transformFor(creativeWork);
|
|
40
|
+
const pipeline = await createSearchIndexer(config, {
|
|
41
|
+
transforms: {
|
|
42
|
+
CreativeWork: [anyUriToIri(schema), ...(transform ? [transform] : [])],
|
|
43
|
+
},
|
|
44
|
+
});
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
`transformFor` answers `undefined` for a root type that does not declare
|
|
48
|
+
`representativeImage` and `associatedMedia`, so a deployment on an older schema loses
|
|
49
|
+
the enrichment rather than the run.
|
|
50
|
+
|
|
51
|
+
| Option | Default | Purpose |
|
|
52
|
+
| --------------- | --------------- | -------------------------------------------------------- |
|
|
53
|
+
| `storePath` | required | SQLite file holding resolved facts between runs |
|
|
54
|
+
| `fetchesPerRun` | 500 | Manifests, and separately `info.json` documents, per run |
|
|
55
|
+
| `report` | console warning | Called for each reference whose fetch failed |
|
|
56
|
+
|
|
57
|
+
`storePath` has to be on a durable volume. The store is the only place these facts
|
|
58
|
+
live between runs, and with an empty one every image is fetched from the publisher
|
|
59
|
+
again.
|
|
60
|
+
|
|
61
|
+
## Consuming the field
|
|
62
|
+
|
|
63
|
+
```json
|
|
64
|
+
{
|
|
65
|
+
"representativeImage": {
|
|
66
|
+
"url": "https://example.org/iiif/image/full/max/0/default.jpg",
|
|
67
|
+
"width": 1335,
|
|
68
|
+
"height": 2750,
|
|
69
|
+
"sizes": [
|
|
70
|
+
{
|
|
71
|
+
"width": 83,
|
|
72
|
+
"height": 172,
|
|
73
|
+
"url": "https://example.org/iiif/image/full/83,172/0/default.jpg"
|
|
74
|
+
}
|
|
75
|
+
],
|
|
76
|
+
"service": {
|
|
77
|
+
"url": "https://example.org/iiif/image"
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
- Pick the smallest entry in `sizes` that is at least your target width. These are
|
|
84
|
+
sizes the server lists or its tiles precompute, so they are served fastest. Do not
|
|
85
|
+
rely on the array’s order.
|
|
86
|
+
- Use `service` only for deep zoom or region crops.
|
|
87
|
+
- `url` is the image as the manifest paints it, at full size.
|
|
88
|
+
- A static image, one without an image service, has only `url`, `width` and
|
|
89
|
+
`height`: no `sizes` and no `service`, because it is the only version there is
|
|
90
|
+
and cannot be resized.
|
|
91
|
+
- Without `representativeImage`, use `associatedMedia[].thumbnailUrl`, with no
|
|
92
|
+
dimensions. That is the case for a work with no manifest, no image in its manifest
|
|
93
|
+
with an image service or a stated size, or a fetch that has not succeeded yet.
|
|
94
|
+
|
|
95
|
+
## Behaviour
|
|
96
|
+
|
|
97
|
+
- **Selection:** the first painted image in the manifest that has an image service
|
|
98
|
+
or whose width and height the manifest states, found by
|
|
99
|
+
[@lde/iiif-image](https://ldelements.org/reference/iiif-image). A static image’s
|
|
100
|
+
size comes from the manifest; an image service’s from its `info.json`. Where that
|
|
101
|
+
`info.json` is gone, the image counts as static, if the manifest states its size.
|
|
102
|
+
An image whose `id` is not an absolute `http(s)` URL is passed over.
|
|
103
|
+
- **Freshness:** manifest facts are refetched after 30 days, because which image
|
|
104
|
+
represents a work can change. `info.json` facts are refetched after 180 days: an
|
|
105
|
+
image’s dimensions do not change, but its server’s size URLs can.
|
|
106
|
+
- **Safety:** fetches go through
|
|
107
|
+
[@lde/public-fetch](https://ldelements.org/reference/public-fetch), which refuses a
|
|
108
|
+
URL, or a redirect target, that is not `http(s)` or names a private address or
|
|
109
|
+
cluster-internal host, and caps a body at 20 MiB. A refused URL or a larger body is
|
|
110
|
+
reported and counts as having no such document. It looks at URLs only, so it does not catch DNS
|
|
111
|
+
pointing inwards. Run the indexer under an egress policy as well.
|
|
112
|
+
- **Failures:** a failed fetch is reported and retried on a later run, and the work
|
|
113
|
+
is indexed without an image. A 401, 403, 404 or 410 counts as having no such
|
|
114
|
+
document and is rechecked after a week, so a brief 401 or 403 can replace a stored
|
|
115
|
+
fact for that long. So does a collection, which has no image of its own, and a
|
|
116
|
+
manifest too broken to read. A 200 that is no IIIF document at all, such as an error
|
|
117
|
+
page served as JSON, counts as a failure, so it is retried every run and five in a
|
|
118
|
+
row stop that dataset’s manifests for the run.
|
|
119
|
+
- **Budget:** `fetchesPerRun` covers all datasets together. What it defers is not
|
|
120
|
+
reported, and those works are indexed without an image until a later run.
|
|
121
|
+
- **Later runs:** retries, deferred works and refreshes all need the dataset to be
|
|
122
|
+
reprocessed. With `PROVENANCE_FILE`, an unchanged dataset waits until it changes
|
|
123
|
+
or `PIPELINE_VERSION` is bumped, so set `fetchesPerRun` high enough to cover it in
|
|
124
|
+
one run.
|
|
125
|
+
- **Limits:** manifest requests are grouped per dataset, because manifest URLs often
|
|
126
|
+
sit on a redirector such as `n2t.net`, and `info.json` requests per host. Datasets
|
|
127
|
+
on one server are limited separately, with only the overall limit across them.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,UAAU,EACV,KAAK,UAAU,EACf,KAAK,iBAAiB,EACtB,KAAK,iBAAiB,EACtB,KAAK,mBAAmB,GACzB,MAAM,sBAAsB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { iiifImages, } from './lib/iiif-images.js';
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { type ResolutionFailure } from '@lde/resolver';
|
|
2
|
+
import type { RootType, SearchSchema } from '@lde/search';
|
|
3
|
+
import type { Quad } from '@rdfjs/types';
|
|
4
|
+
export interface IiifImagesOptions {
|
|
5
|
+
/**
|
|
6
|
+
* The SQLite file resolved facts live in between runs. It has to survive the
|
|
7
|
+
* run: the search index is rebuilt from it, and nothing else holds these facts.
|
|
8
|
+
*/
|
|
9
|
+
readonly storePath: string;
|
|
10
|
+
/**
|
|
11
|
+
* Manifests, and separately `info.json` documents, this object may fetch across
|
|
12
|
+
* all datasets, so construct one per run. Defaults to the resolver’s 500.
|
|
13
|
+
*
|
|
14
|
+
* What the budget defers is not reported, and those works are indexed without an
|
|
15
|
+
* image. A cold store then warms only as datasets are reprocessed, so where
|
|
16
|
+
* unchanged datasets are skipped, set this high enough to cover them in one run.
|
|
17
|
+
*/
|
|
18
|
+
readonly fetchesPerRun?: number;
|
|
19
|
+
/** Told about each reference that failed. Defaults to a console warning. */
|
|
20
|
+
readonly report?: (failure: ResolutionFailure) => void;
|
|
21
|
+
}
|
|
22
|
+
/** What the transform reads from the reader it is attached to. */
|
|
23
|
+
export interface IiifImagesContext {
|
|
24
|
+
readonly dataset: {
|
|
25
|
+
readonly iri: URL;
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
/** A transform attachable to a reader as an `@lde/pipeline` `QuadTransform`. */
|
|
29
|
+
export type IiifImagesTransform = (quads: AsyncIterable<Quad>, context: IiifImagesContext) => AsyncIterable<Quad>;
|
|
30
|
+
export interface IiifImages {
|
|
31
|
+
/**
|
|
32
|
+
* The transform to attach to a root type’s reader, after any transform that
|
|
33
|
+
* repairs IRIs, or `undefined` where that type does not declare
|
|
34
|
+
* `representativeImage` and `associatedMedia` – so a deployment on an older
|
|
35
|
+
* schema loses the enrichment rather than the run.
|
|
36
|
+
*/
|
|
37
|
+
transformFor(rootType: RootType): IiifImagesTransform | undefined;
|
|
38
|
+
/** Throws when the store cannot persist, to fail a run before it starts. */
|
|
39
|
+
check(): Promise<void>;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Enrich works with the image that represents them: its URL and dimensions, and
|
|
43
|
+
* for an image with an image service, a ladder of precomputed sizes, resolved
|
|
44
|
+
* from the work’s IIIF Presentation manifest and the selected image’s
|
|
45
|
+
* `info.json`. A static image is read from the manifest alone.
|
|
46
|
+
*
|
|
47
|
+
* A work with no manifest, or whose resolution has not succeeded, gets no
|
|
48
|
+
* `representativeImage`; consumers then fall back to the `thumbnailUrl` already
|
|
49
|
+
* in `associatedMedia`, and a failed resolution is retried on a later run.
|
|
50
|
+
*/
|
|
51
|
+
export declare function iiifImages(schema: SearchSchema, options: IiifImagesOptions): IiifImages;
|
|
52
|
+
//# sourceMappingURL=iiif-images.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"iiif-images.d.ts","sourceRoot":"","sources":["../../src/lib/iiif-images.ts"],"names":[],"mappings":"AAWA,OAAO,EAKL,KAAK,iBAAiB,EAEvB,MAAM,eAAe,CAAC;AACvB,OAAO,KAAK,EACV,QAAQ,EAER,YAAY,EAEb,MAAM,aAAa,CAAC;AAErB,OAAO,KAAK,EAAE,IAAI,EAA6B,MAAM,cAAc,CAAC;AASpE,MAAM,WAAW,iBAAiB;IAChC;;;OAGG;IACH,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B;;;;;;;OAOG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC,4EAA4E;IAC5E,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,iBAAiB,KAAK,IAAI,CAAC;CACxD;AAED,kEAAkE;AAClE,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,OAAO,EAAE;QAAE,QAAQ,CAAC,GAAG,EAAE,GAAG,CAAA;KAAE,CAAC;CACzC;AAED,gFAAgF;AAChF,MAAM,MAAM,mBAAmB,GAAG,CAChC,KAAK,EAAE,aAAa,CAAC,IAAI,CAAC,EAC1B,OAAO,EAAE,iBAAiB,KACvB,aAAa,CAAC,IAAI,CAAC,CAAC;AAEzB,MAAM,WAAW,UAAU;IACzB;;;;;OAKG;IACH,YAAY,CAAC,QAAQ,EAAE,QAAQ,GAAG,mBAAmB,GAAG,SAAS,CAAC;IAClE,4EAA4E;IAC5E,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAED;;;;;;;;;GASG;AACH,wBAAgB,UAAU,CACxB,MAAM,EAAE,YAAY,EACpB,OAAO,EAAE,iBAAiB,GACzB,UAAU,CAoHZ"}
|
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
import { images, readImageService, } from '@lde/iiif-image';
|
|
2
|
+
import { fetchPublic, RefusedUrlError, ResponseTooLargeError, } from '@lde/public-fetch';
|
|
3
|
+
import { createResolver, sqliteFactStore, urlHost, } from '@lde/resolver';
|
|
4
|
+
import { fieldNamed, irAlias, referenceTypeNamed } from '@lde/search/adapter';
|
|
5
|
+
import { DataFactory } from 'rdf-data-factory';
|
|
6
|
+
import { iiifImage, iiifImageService, iiifImageSize, representativeImageField, } from '../schema.js';
|
|
7
|
+
/**
|
|
8
|
+
* Enrich works with the image that represents them: its URL and dimensions, and
|
|
9
|
+
* for an image with an image service, a ladder of precomputed sizes, resolved
|
|
10
|
+
* from the work’s IIIF Presentation manifest and the selected image’s
|
|
11
|
+
* `info.json`. A static image is read from the manifest alone.
|
|
12
|
+
*
|
|
13
|
+
* A work with no manifest, or whose resolution has not succeeded, gets no
|
|
14
|
+
* `representativeImage`; consumers then fall back to the `thumbnailUrl` already
|
|
15
|
+
* in `associatedMedia`, and a failed resolution is retried on a later run.
|
|
16
|
+
*/
|
|
17
|
+
export function iiifImages(schema, options) {
|
|
18
|
+
const limits = options.fetchesPerRun === undefined
|
|
19
|
+
? {}
|
|
20
|
+
: { fetchesPerResolver: options.fetchesPerRun };
|
|
21
|
+
const report = options.report ??
|
|
22
|
+
((failure) => console.warn(`${failure.key}: ${failure.reason}`));
|
|
23
|
+
const manifests = createResolver({
|
|
24
|
+
key: (reference) => reference.url,
|
|
25
|
+
// Not the manifest’s own host: many are on n2t.net, which only redirects, so
|
|
26
|
+
// its limits and failure cut-off would land on the wrong server and lump
|
|
27
|
+
// publishers together. A dataset is the closest known stand-in for the server
|
|
28
|
+
// at the end of those redirects.
|
|
29
|
+
host: (reference) => reference.dataset,
|
|
30
|
+
fetch: (references, { signal }) => fetchFacts(references.map(({ url }) => url), (url) => url, (document) => {
|
|
31
|
+
try {
|
|
32
|
+
return firstUsable(images(document));
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
// A document that says it is a collection, or a manifest too broken
|
|
36
|
+
// to read, has no image to offer: a fact about that document. Anything
|
|
37
|
+
// else – an error page served as JSON – is a server misbehaving, so
|
|
38
|
+
// no absence may overwrite the stored fact.
|
|
39
|
+
if (error instanceof TypeError && declaresIiifType(document)) {
|
|
40
|
+
return undefined;
|
|
41
|
+
}
|
|
42
|
+
throw error;
|
|
43
|
+
}
|
|
44
|
+
}, signal, report),
|
|
45
|
+
store: sqliteFactStore({
|
|
46
|
+
path: options.storePath,
|
|
47
|
+
table: 'manifests',
|
|
48
|
+
}),
|
|
49
|
+
// 2: the selected image rather than its service’s URL, and static images too.
|
|
50
|
+
version: '2',
|
|
51
|
+
limits: { ...limits, ttlMs: MANIFEST_TTL_MS },
|
|
52
|
+
report,
|
|
53
|
+
});
|
|
54
|
+
const services = createResolver({
|
|
55
|
+
key: (service) => service,
|
|
56
|
+
host: urlHost,
|
|
57
|
+
fetch: (services, { signal }) => fetchFacts(services, (service) => `${service}/info.json`, (info, service) => {
|
|
58
|
+
// Same reasoning, with more at stake: these facts are otherwise permanent.
|
|
59
|
+
const fact = readImageService(service, info);
|
|
60
|
+
if (fact === undefined) {
|
|
61
|
+
throw new Error(`${service}/info.json states no dimensions`);
|
|
62
|
+
}
|
|
63
|
+
return fact;
|
|
64
|
+
}, signal, report),
|
|
65
|
+
store: sqliteFactStore({
|
|
66
|
+
path: options.storePath,
|
|
67
|
+
table: 'images',
|
|
68
|
+
}),
|
|
69
|
+
limits: { ...limits, ttlMs: IMAGE_TTL_MS },
|
|
70
|
+
report,
|
|
71
|
+
});
|
|
72
|
+
return {
|
|
73
|
+
transformFor(rootType) {
|
|
74
|
+
const aliases = aliasesOf(rootType, schema);
|
|
75
|
+
return aliases === undefined
|
|
76
|
+
? undefined
|
|
77
|
+
: async function* enrich(quads, context) {
|
|
78
|
+
// A batch of roots is the unit of work and what bounds the memory: a
|
|
79
|
+
// work’s manifest entry is only known once the batch is, and resolving
|
|
80
|
+
// per batch is what lets the resolver share one store read.
|
|
81
|
+
const batch = [];
|
|
82
|
+
for await (const quad of quads) {
|
|
83
|
+
batch.push(quad);
|
|
84
|
+
yield quad;
|
|
85
|
+
}
|
|
86
|
+
const manifestOfWork = manifestsIn(batch, aliases);
|
|
87
|
+
const dataset = context.dataset.iri.toString();
|
|
88
|
+
const selected = await manifests.resolveAll([...manifestOfWork.values()].map((url) => ({ url, dataset })));
|
|
89
|
+
const imageOfWork = new Map();
|
|
90
|
+
for (const [work, manifest] of manifestOfWork) {
|
|
91
|
+
const resolution = selected.get(manifest);
|
|
92
|
+
if (resolution?.outcome === 'resolved') {
|
|
93
|
+
imageOfWork.set(work, resolution.fact);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
const resolved = await services.resolveAll([...imageOfWork.values()].flatMap(({ service }) => service === undefined ? [] : [service]));
|
|
97
|
+
for (const [work, image] of imageOfWork) {
|
|
98
|
+
const representative = representativeOf(image, resolved);
|
|
99
|
+
if (representative !== undefined) {
|
|
100
|
+
yield* mint(factory.namedNode(work), representative, aliases);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
},
|
|
105
|
+
async check() {
|
|
106
|
+
await manifests.check();
|
|
107
|
+
await services.check();
|
|
108
|
+
},
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
const factory = new DataFactory();
|
|
112
|
+
const INTEGER = factory.namedNode('http://www.w3.org/2001/XMLSchema#integer');
|
|
113
|
+
/** What the profile’s `encodingFormat` for a manifest entry contains. */
|
|
114
|
+
const IIIF_PRESENTATION_CONTEXT = 'iiif.io/api/presentation';
|
|
115
|
+
/** A manifest’s selection can change, so it is reconsidered after 30 days. */
|
|
116
|
+
const MANIFEST_TTL_MS = 30 * 24 * 3_600_000;
|
|
117
|
+
/**
|
|
118
|
+
* An image’s dimensions do not change, but what its server offers does: an upgrade
|
|
119
|
+
* can change the size URLs stored with them. So these facts are reconsidered too,
|
|
120
|
+
* only rarely.
|
|
121
|
+
*/
|
|
122
|
+
const IMAGE_TTL_MS = 180 * 24 * 3_600_000;
|
|
123
|
+
const IIIF_TYPES = new Set([
|
|
124
|
+
'Manifest',
|
|
125
|
+
'Collection',
|
|
126
|
+
'sc:Manifest',
|
|
127
|
+
'sc:Collection',
|
|
128
|
+
]);
|
|
129
|
+
/** Answers meaning this document is not ours to have, now or on a retry. */
|
|
130
|
+
const ABSENT_STATUSES = new Set([401, 403, 404, 410]);
|
|
131
|
+
/**
|
|
132
|
+
* The first image that can represent the work: one with an image service, whose
|
|
133
|
+
* `info.json` states its dimensions, or a static image whose manifest states
|
|
134
|
+
* them. Without a size to state, a static image adds nothing to the
|
|
135
|
+
* `thumbnailUrl` consumers already have. Its URL is minted as an IRI, so an image
|
|
136
|
+
* whose `id` is not an absolute web address is passed over.
|
|
137
|
+
*/
|
|
138
|
+
function firstUsable(painted) {
|
|
139
|
+
for (const image of painted) {
|
|
140
|
+
const url = webAddress(image.url);
|
|
141
|
+
if (url !== undefined &&
|
|
142
|
+
(image.service !== undefined || staticOf(image) !== undefined)) {
|
|
143
|
+
return { ...image, url };
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return undefined;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* The image to mint, or `undefined` while its image service is not resolved.
|
|
150
|
+
* An image service found to be gone leaves the size the manifest states, if any.
|
|
151
|
+
*/
|
|
152
|
+
function representativeOf(image, resolved) {
|
|
153
|
+
const resolution = image.service === undefined ? undefined : resolved.get(image.service);
|
|
154
|
+
if (resolution?.outcome === 'resolved') {
|
|
155
|
+
return { ...resolution.fact, url: image.url, service: resolution.fact.url };
|
|
156
|
+
}
|
|
157
|
+
return image.service === undefined || resolution?.outcome === 'absent'
|
|
158
|
+
? staticOf(image)
|
|
159
|
+
: undefined;
|
|
160
|
+
}
|
|
161
|
+
/** The image as the manifest states it, without sizes or a service. */
|
|
162
|
+
function staticOf(image) {
|
|
163
|
+
return image.width === undefined || image.height === undefined
|
|
164
|
+
? undefined
|
|
165
|
+
: { url: image.url, width: image.width, height: image.height, sizes: [] };
|
|
166
|
+
}
|
|
167
|
+
/** The URL, normalised, if it is an absolute `http(s)` one. */
|
|
168
|
+
function webAddress(url) {
|
|
169
|
+
if (!URL.canParse(url)) {
|
|
170
|
+
return undefined;
|
|
171
|
+
}
|
|
172
|
+
const { protocol, href } = new URL(url);
|
|
173
|
+
return protocol === 'http:' || protocol === 'https:' ? href : undefined;
|
|
174
|
+
}
|
|
175
|
+
function aliasesOf(rootType, schema) {
|
|
176
|
+
const media = fieldNamed(rootType, 'associatedMedia');
|
|
177
|
+
const image = fieldNamed(rootType, representativeImageField.name);
|
|
178
|
+
const mediaType = referencedType(schema, media);
|
|
179
|
+
const encodingFormat = mediaType === undefined ? undefined : aliasOf(mediaType, 'encodingFormat');
|
|
180
|
+
const imageType = referencedType(schema, image);
|
|
181
|
+
const sizeType = imageType && referencedType(schema, fieldNamed(imageType, 'sizes'));
|
|
182
|
+
const serviceType = imageType && referencedType(schema, fieldNamed(imageType, 'service'));
|
|
183
|
+
if (media === undefined ||
|
|
184
|
+
image === undefined ||
|
|
185
|
+
encodingFormat === undefined ||
|
|
186
|
+
imageType === undefined ||
|
|
187
|
+
sizeType === undefined ||
|
|
188
|
+
serviceType === undefined) {
|
|
189
|
+
return undefined;
|
|
190
|
+
}
|
|
191
|
+
const imageAliases = fieldAliases(imageType, iiifImage);
|
|
192
|
+
const sizeAliases = fieldAliases(sizeType, iiifImageSize);
|
|
193
|
+
const serviceAliases = fieldAliases(serviceType, iiifImageService);
|
|
194
|
+
return imageAliases === undefined ||
|
|
195
|
+
sizeAliases === undefined ||
|
|
196
|
+
serviceAliases === undefined
|
|
197
|
+
? undefined
|
|
198
|
+
: {
|
|
199
|
+
associatedMedia: irAlias(rootType, media),
|
|
200
|
+
encodingFormat,
|
|
201
|
+
representativeImage: irAlias(rootType, image),
|
|
202
|
+
image: imageAliases,
|
|
203
|
+
size: sizeAliases,
|
|
204
|
+
service: serviceAliases,
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* The type a reference field nests. Read from the declaration rather than through
|
|
209
|
+
* `nestedReferenceType`, which answers only for surfaced fields: a schema may read
|
|
210
|
+
* `associatedMedia` without surfacing it, and should still be enriched.
|
|
211
|
+
*/
|
|
212
|
+
function referencedType(schema, field) {
|
|
213
|
+
return field?.kind === 'reference' && field.ref?.strategy === 'inline'
|
|
214
|
+
? referenceTypeNamed(schema, field.ref.typeName)
|
|
215
|
+
: undefined;
|
|
216
|
+
}
|
|
217
|
+
/** Every field `declaration` names, as the schema’s own `searchType` aliases it,
|
|
218
|
+
* or `undefined` where that type lacks one. */
|
|
219
|
+
function fieldAliases(searchType, declaration) {
|
|
220
|
+
const entries = declaration.fields.map(({ name }) => [name, aliasOf(searchType, name)]);
|
|
221
|
+
return entries.every(([, alias]) => alias !== undefined)
|
|
222
|
+
? Object.fromEntries(entries)
|
|
223
|
+
: undefined;
|
|
224
|
+
}
|
|
225
|
+
function aliasOf(searchType, name) {
|
|
226
|
+
const field = fieldNamed(searchType, name);
|
|
227
|
+
return field === undefined ? undefined : irAlias(searchType, field);
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Each work’s manifest: its `associatedMedia` entry that is a named node and names
|
|
231
|
+
* the IIIF Presentation context in its `encodingFormat`. The profile allows one;
|
|
232
|
+
* where a publisher states more, the first by IRI, because quad order can change
|
|
233
|
+
* between runs and the representative image should not change with it. Only works
|
|
234
|
+
* named by an IRI, because the image is minted onto the work by its name.
|
|
235
|
+
*/
|
|
236
|
+
function manifestsIn(batch, aliases) {
|
|
237
|
+
const manifestEntries = new Set(batch
|
|
238
|
+
.filter((quad) => quad.predicate.value === aliases.encodingFormat &&
|
|
239
|
+
quad.subject.termType === 'NamedNode' &&
|
|
240
|
+
quad.object.value.includes(IIIF_PRESENTATION_CONTEXT))
|
|
241
|
+
.map((quad) => quad.subject.value));
|
|
242
|
+
const manifestOfWork = new Map();
|
|
243
|
+
for (const quad of batch) {
|
|
244
|
+
if (quad.predicate.value !== aliases.associatedMedia ||
|
|
245
|
+
quad.subject.termType !== 'NamedNode' ||
|
|
246
|
+
!manifestEntries.has(quad.object.value)) {
|
|
247
|
+
continue;
|
|
248
|
+
}
|
|
249
|
+
const current = manifestOfWork.get(quad.subject.value);
|
|
250
|
+
if (current === undefined || quad.object.value < current) {
|
|
251
|
+
manifestOfWork.set(quad.subject.value, quad.object.value);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
return manifestOfWork;
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Fetch each key’s JSON document and derive its fact. An {@link ABSENT_STATUSES}
|
|
258
|
+
* answer, or a document `derive` finds no fact in, is an absence and left out;
|
|
259
|
+
* anything else throws, so the resolver retries it and a stored fact survives a
|
|
260
|
+
* passing outage. Restricted images are absences so that they cannot write off a
|
|
261
|
+
* host whose public images are fine. So, reported, is a URL that is not a public
|
|
262
|
+
* web address – publisher data decides these URLs, and this runs inside the
|
|
263
|
+
* cluster – and a document too large to read, which a retry would not shrink.
|
|
264
|
+
*/
|
|
265
|
+
async function fetchFacts(keys, urlOf, derive, signal, report) {
|
|
266
|
+
const facts = [];
|
|
267
|
+
for (const key of keys) {
|
|
268
|
+
let document;
|
|
269
|
+
try {
|
|
270
|
+
document = await fetchDocument(urlOf(key), signal);
|
|
271
|
+
}
|
|
272
|
+
catch (error) {
|
|
273
|
+
if (!(error instanceof RefusedUrlError) &&
|
|
274
|
+
!(error instanceof ResponseTooLargeError)) {
|
|
275
|
+
throw error;
|
|
276
|
+
}
|
|
277
|
+
report({ key, reason: error.message, degradedToStore: false });
|
|
278
|
+
continue;
|
|
279
|
+
}
|
|
280
|
+
const fact = document === undefined ? undefined : derive(document, key);
|
|
281
|
+
if (fact !== undefined) {
|
|
282
|
+
facts.push([key, fact]);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
return facts;
|
|
286
|
+
}
|
|
287
|
+
/** The JSON document at `url`, or `undefined` for an {@link ABSENT_STATUSES} answer. */
|
|
288
|
+
async function fetchDocument(url, signal) {
|
|
289
|
+
// Only `*/*`: LDmaX answers 404 to any JSON media type in `Accept`, IIIF’s own
|
|
290
|
+
// included, even with `*/*` as a fallback beside it.
|
|
291
|
+
const response = await fetchPublic(url, {
|
|
292
|
+
signal,
|
|
293
|
+
headers: { Accept: '*/*' },
|
|
294
|
+
});
|
|
295
|
+
if (!response.ok) {
|
|
296
|
+
// An unread body keeps its connection until garbage collection.
|
|
297
|
+
await response.body?.cancel();
|
|
298
|
+
if (ABSENT_STATUSES.has(response.status)) {
|
|
299
|
+
return undefined;
|
|
300
|
+
}
|
|
301
|
+
throw new Error(`${url} answered ${response.status}`);
|
|
302
|
+
}
|
|
303
|
+
return response.json();
|
|
304
|
+
}
|
|
305
|
+
/** Whether a document says it is a IIIF manifest or collection, in Presentation 3 or 2. */
|
|
306
|
+
function declaresIiifType(document) {
|
|
307
|
+
if (typeof document !== 'object' || document === null) {
|
|
308
|
+
return false;
|
|
309
|
+
}
|
|
310
|
+
const { type, '@type': legacyType } = document;
|
|
311
|
+
return IIIF_TYPES.has(String(type ?? legacyType));
|
|
312
|
+
}
|
|
313
|
+
function* mint(work, fact, aliases) {
|
|
314
|
+
const image = factory.blankNode();
|
|
315
|
+
yield triple(work, aliases.representativeImage, image);
|
|
316
|
+
yield triple(image, aliases.image.url, factory.namedNode(fact.url));
|
|
317
|
+
yield triple(image, aliases.image.width, integer(fact.width));
|
|
318
|
+
yield triple(image, aliases.image.height, integer(fact.height));
|
|
319
|
+
for (const size of fact.sizes) {
|
|
320
|
+
const node = factory.blankNode();
|
|
321
|
+
yield triple(image, aliases.image.sizes, node);
|
|
322
|
+
yield triple(node, aliases.size.width, integer(size.width));
|
|
323
|
+
yield triple(node, aliases.size.height, integer(size.height));
|
|
324
|
+
yield triple(node, aliases.size.url, factory.namedNode(size.url));
|
|
325
|
+
}
|
|
326
|
+
if (fact.service !== undefined) {
|
|
327
|
+
const service = factory.blankNode();
|
|
328
|
+
yield triple(image, aliases.image.service, service);
|
|
329
|
+
yield triple(service, aliases.service.url, factory.namedNode(fact.service));
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
function triple(subject, predicate, object) {
|
|
333
|
+
return factory.quad(subject, factory.namedNode(predicate), object);
|
|
334
|
+
}
|
|
335
|
+
function integer(value) {
|
|
336
|
+
return factory.literal(String(value), INTEGER);
|
|
337
|
+
}
|
package/dist/schema.d.ts
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
/** One precomputed rendition of the image, with the URL to request it at. */
|
|
2
|
+
export declare const iiifImageSize: {
|
|
3
|
+
readonly name: "IiifImageSize";
|
|
4
|
+
readonly fields: readonly [{
|
|
5
|
+
readonly name: "width";
|
|
6
|
+
readonly kind: "integer";
|
|
7
|
+
readonly output: true;
|
|
8
|
+
readonly path: "<https://schema.org/width>";
|
|
9
|
+
}, {
|
|
10
|
+
readonly name: "height";
|
|
11
|
+
readonly kind: "integer";
|
|
12
|
+
readonly output: true;
|
|
13
|
+
readonly path: "<https://schema.org/height>";
|
|
14
|
+
}, {
|
|
15
|
+
readonly name: "url";
|
|
16
|
+
readonly kind: "reference";
|
|
17
|
+
readonly output: true;
|
|
18
|
+
readonly path: "<https://schema.org/contentUrl>";
|
|
19
|
+
readonly ref: {
|
|
20
|
+
readonly strategy: "idOnly";
|
|
21
|
+
};
|
|
22
|
+
}];
|
|
23
|
+
};
|
|
24
|
+
/** The IIIF image service itself, for consumers doing deep zoom or region crops. */
|
|
25
|
+
export declare const iiifImageService: {
|
|
26
|
+
readonly name: "IiifImageService";
|
|
27
|
+
readonly fields: readonly [{
|
|
28
|
+
readonly name: "url";
|
|
29
|
+
readonly kind: "reference";
|
|
30
|
+
readonly output: true;
|
|
31
|
+
readonly path: "<https://schema.org/url>";
|
|
32
|
+
readonly ref: {
|
|
33
|
+
readonly strategy: "idOnly";
|
|
34
|
+
};
|
|
35
|
+
}];
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* The image that represents a work: the image the manifest paints, its full
|
|
39
|
+
* dimensions, and the renditions a consumer can pick from without knowing Image
|
|
40
|
+
* API compliance levels.
|
|
41
|
+
*/
|
|
42
|
+
export declare const iiifImage: {
|
|
43
|
+
readonly name: "IiifImage";
|
|
44
|
+
readonly fields: readonly [{
|
|
45
|
+
readonly name: "url";
|
|
46
|
+
readonly kind: "reference";
|
|
47
|
+
readonly output: true;
|
|
48
|
+
readonly path: "<https://schema.org/contentUrl>";
|
|
49
|
+
readonly ref: {
|
|
50
|
+
readonly strategy: "idOnly";
|
|
51
|
+
};
|
|
52
|
+
}, {
|
|
53
|
+
readonly name: "width";
|
|
54
|
+
readonly kind: "integer";
|
|
55
|
+
readonly output: true;
|
|
56
|
+
readonly path: "<https://schema.org/width>";
|
|
57
|
+
}, {
|
|
58
|
+
readonly name: "height";
|
|
59
|
+
readonly kind: "integer";
|
|
60
|
+
readonly output: true;
|
|
61
|
+
readonly path: "<https://schema.org/height>";
|
|
62
|
+
}, {
|
|
63
|
+
readonly name: "sizes";
|
|
64
|
+
readonly kind: "reference";
|
|
65
|
+
readonly array: true;
|
|
66
|
+
readonly output: true;
|
|
67
|
+
readonly path: "<https://def.nde.nl/iiif#size>";
|
|
68
|
+
readonly ref: {
|
|
69
|
+
readonly typeName: "IiifImageSize";
|
|
70
|
+
readonly strategy: "inline";
|
|
71
|
+
};
|
|
72
|
+
}, {
|
|
73
|
+
readonly name: "service";
|
|
74
|
+
readonly kind: "reference";
|
|
75
|
+
readonly output: true;
|
|
76
|
+
readonly path: "<https://def.nde.nl/iiif#service>";
|
|
77
|
+
readonly ref: {
|
|
78
|
+
readonly typeName: "IiifImageService";
|
|
79
|
+
readonly strategy: "inline";
|
|
80
|
+
};
|
|
81
|
+
}];
|
|
82
|
+
};
|
|
83
|
+
/** The types {@link representativeImageField} nests, to add to the schema. */
|
|
84
|
+
export declare const iiifImageTypes: ({
|
|
85
|
+
readonly name: "IiifImageSize";
|
|
86
|
+
readonly fields: readonly [{
|
|
87
|
+
readonly name: "width";
|
|
88
|
+
readonly kind: "integer";
|
|
89
|
+
readonly output: true;
|
|
90
|
+
readonly path: "<https://schema.org/width>";
|
|
91
|
+
}, {
|
|
92
|
+
readonly name: "height";
|
|
93
|
+
readonly kind: "integer";
|
|
94
|
+
readonly output: true;
|
|
95
|
+
readonly path: "<https://schema.org/height>";
|
|
96
|
+
}, {
|
|
97
|
+
readonly name: "url";
|
|
98
|
+
readonly kind: "reference";
|
|
99
|
+
readonly output: true;
|
|
100
|
+
readonly path: "<https://schema.org/contentUrl>";
|
|
101
|
+
readonly ref: {
|
|
102
|
+
readonly strategy: "idOnly";
|
|
103
|
+
};
|
|
104
|
+
}];
|
|
105
|
+
} | {
|
|
106
|
+
readonly name: "IiifImageService";
|
|
107
|
+
readonly fields: readonly [{
|
|
108
|
+
readonly name: "url";
|
|
109
|
+
readonly kind: "reference";
|
|
110
|
+
readonly output: true;
|
|
111
|
+
readonly path: "<https://schema.org/url>";
|
|
112
|
+
readonly ref: {
|
|
113
|
+
readonly strategy: "idOnly";
|
|
114
|
+
};
|
|
115
|
+
}];
|
|
116
|
+
} | {
|
|
117
|
+
readonly name: "IiifImage";
|
|
118
|
+
readonly fields: readonly [{
|
|
119
|
+
readonly name: "url";
|
|
120
|
+
readonly kind: "reference";
|
|
121
|
+
readonly output: true;
|
|
122
|
+
readonly path: "<https://schema.org/contentUrl>";
|
|
123
|
+
readonly ref: {
|
|
124
|
+
readonly strategy: "idOnly";
|
|
125
|
+
};
|
|
126
|
+
}, {
|
|
127
|
+
readonly name: "width";
|
|
128
|
+
readonly kind: "integer";
|
|
129
|
+
readonly output: true;
|
|
130
|
+
readonly path: "<https://schema.org/width>";
|
|
131
|
+
}, {
|
|
132
|
+
readonly name: "height";
|
|
133
|
+
readonly kind: "integer";
|
|
134
|
+
readonly output: true;
|
|
135
|
+
readonly path: "<https://schema.org/height>";
|
|
136
|
+
}, {
|
|
137
|
+
readonly name: "sizes";
|
|
138
|
+
readonly kind: "reference";
|
|
139
|
+
readonly array: true;
|
|
140
|
+
readonly output: true;
|
|
141
|
+
readonly path: "<https://def.nde.nl/iiif#size>";
|
|
142
|
+
readonly ref: {
|
|
143
|
+
readonly typeName: "IiifImageSize";
|
|
144
|
+
readonly strategy: "inline";
|
|
145
|
+
};
|
|
146
|
+
}, {
|
|
147
|
+
readonly name: "service";
|
|
148
|
+
readonly kind: "reference";
|
|
149
|
+
readonly output: true;
|
|
150
|
+
readonly path: "<https://def.nde.nl/iiif#service>";
|
|
151
|
+
readonly ref: {
|
|
152
|
+
readonly typeName: "IiifImageService";
|
|
153
|
+
readonly strategy: "inline";
|
|
154
|
+
};
|
|
155
|
+
}];
|
|
156
|
+
})[];
|
|
157
|
+
/** Add to a root type’s fields, and {@link iiifImageTypes} to the schema, to opt
|
|
158
|
+
* that type in to enrichment. */
|
|
159
|
+
export declare const representativeImageField: {
|
|
160
|
+
readonly name: "representativeImage";
|
|
161
|
+
readonly kind: "reference";
|
|
162
|
+
readonly output: true;
|
|
163
|
+
readonly path: "<https://def.nde.nl/iiif#representativeImage>";
|
|
164
|
+
readonly ref: {
|
|
165
|
+
readonly typeName: "IiifImage";
|
|
166
|
+
readonly strategy: "inline";
|
|
167
|
+
};
|
|
168
|
+
};
|
|
169
|
+
//# sourceMappingURL=schema.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"AAKA,6EAA6E;AAC7E,eAAO,MAAM,aAAa;;;;;;;;;;;;;;;;;;;;;CAkBK,CAAC;AAEhC,oFAAoF;AACpF,eAAO,MAAM,gBAAgB;;;;;;;;;;;CAWE,CAAC;AAEhC;;;;GAIG;AACH,eAAO,MAAM,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAiCS,CAAC;AAEhC,8EAA8E;AAC9E,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAA+C,CAAC;AAE3E;kCACkC;AAClC,eAAO,MAAM,wBAAwB;;;;;;;;;CAML,CAAC"}
|
package/dist/schema.js
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
const SCHEMA = 'https://schema.org/';
|
|
2
|
+
const IIIF = 'https://def.nde.nl/iiif#';
|
|
3
|
+
/** One precomputed rendition of the image, with the URL to request it at. */
|
|
4
|
+
export const iiifImageSize = {
|
|
5
|
+
name: 'IiifImageSize',
|
|
6
|
+
fields: [
|
|
7
|
+
{ name: 'width', kind: 'integer', output: true, path: `<${SCHEMA}width>` },
|
|
8
|
+
{
|
|
9
|
+
name: 'height',
|
|
10
|
+
kind: 'integer',
|
|
11
|
+
output: true,
|
|
12
|
+
path: `<${SCHEMA}height>`,
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
name: 'url',
|
|
16
|
+
kind: 'reference',
|
|
17
|
+
output: true,
|
|
18
|
+
path: `<${SCHEMA}contentUrl>`,
|
|
19
|
+
ref: { strategy: 'idOnly' },
|
|
20
|
+
},
|
|
21
|
+
],
|
|
22
|
+
};
|
|
23
|
+
/** The IIIF image service itself, for consumers doing deep zoom or region crops. */
|
|
24
|
+
export const iiifImageService = {
|
|
25
|
+
name: 'IiifImageService',
|
|
26
|
+
fields: [
|
|
27
|
+
{
|
|
28
|
+
name: 'url',
|
|
29
|
+
kind: 'reference',
|
|
30
|
+
output: true,
|
|
31
|
+
path: `<${SCHEMA}url>`,
|
|
32
|
+
ref: { strategy: 'idOnly' },
|
|
33
|
+
},
|
|
34
|
+
],
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* The image that represents a work: the image the manifest paints, its full
|
|
38
|
+
* dimensions, and the renditions a consumer can pick from without knowing Image
|
|
39
|
+
* API compliance levels.
|
|
40
|
+
*/
|
|
41
|
+
export const iiifImage = {
|
|
42
|
+
name: 'IiifImage',
|
|
43
|
+
fields: [
|
|
44
|
+
{
|
|
45
|
+
name: 'url',
|
|
46
|
+
kind: 'reference',
|
|
47
|
+
output: true,
|
|
48
|
+
path: `<${SCHEMA}contentUrl>`,
|
|
49
|
+
ref: { strategy: 'idOnly' },
|
|
50
|
+
},
|
|
51
|
+
{ name: 'width', kind: 'integer', output: true, path: `<${SCHEMA}width>` },
|
|
52
|
+
{
|
|
53
|
+
name: 'height',
|
|
54
|
+
kind: 'integer',
|
|
55
|
+
output: true,
|
|
56
|
+
path: `<${SCHEMA}height>`,
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
name: 'sizes',
|
|
60
|
+
kind: 'reference',
|
|
61
|
+
array: true,
|
|
62
|
+
output: true,
|
|
63
|
+
path: `<${IIIF}size>`,
|
|
64
|
+
ref: { typeName: iiifImageSize.name, strategy: 'inline' },
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
name: 'service',
|
|
68
|
+
kind: 'reference',
|
|
69
|
+
output: true,
|
|
70
|
+
path: `<${IIIF}service>`,
|
|
71
|
+
ref: { typeName: iiifImageService.name, strategy: 'inline' },
|
|
72
|
+
},
|
|
73
|
+
],
|
|
74
|
+
};
|
|
75
|
+
/** The types {@link representativeImageField} nests, to add to the schema. */
|
|
76
|
+
export const iiifImageTypes = [iiifImage, iiifImageSize, iiifImageService];
|
|
77
|
+
/** Add to a root type’s fields, and {@link iiifImageTypes} to the schema, to opt
|
|
78
|
+
* that type in to enrichment. */
|
|
79
|
+
export const representativeImageField = {
|
|
80
|
+
name: 'representativeImage',
|
|
81
|
+
kind: 'reference',
|
|
82
|
+
output: true,
|
|
83
|
+
path: `<${IIIF}representativeImage>`,
|
|
84
|
+
ref: { typeName: iiifImage.name, strategy: 'inline' },
|
|
85
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,57 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ndes/iiif-images",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Enriches SCHEMA-AP-NDE works with the dimensions and sizes of their representative IIIF image, as a quad transform for the search indexer.",
|
|
5
|
+
"repository": {
|
|
6
|
+
"url": "git+https://github.com/netwerk-digitaal-erfgoed/stack.git",
|
|
7
|
+
"directory": "packages/iiif-images"
|
|
8
|
+
},
|
|
9
|
+
"license": "MIT",
|
|
10
|
+
"type": "module",
|
|
11
|
+
"exports": {
|
|
12
|
+
"./package.json": "./package.json",
|
|
13
|
+
".": {
|
|
14
|
+
"@ndes/source": "./src/index.ts",
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"import": "./dist/index.js",
|
|
17
|
+
"default": "./dist/index.js"
|
|
18
|
+
},
|
|
19
|
+
"./schema": {
|
|
20
|
+
"@ndes/source": "./src/schema.ts",
|
|
21
|
+
"types": "./dist/schema.d.ts",
|
|
22
|
+
"import": "./dist/schema.js",
|
|
23
|
+
"default": "./dist/schema.js"
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"main": "./dist/index.js",
|
|
27
|
+
"module": "./dist/index.js",
|
|
28
|
+
"types": "./dist/index.d.ts",
|
|
29
|
+
"files": [
|
|
30
|
+
"dist",
|
|
31
|
+
"!**/*.tsbuildinfo"
|
|
32
|
+
],
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"@lde/iiif-image": "^0.2.0",
|
|
35
|
+
"@lde/public-fetch": "^0.1.0",
|
|
36
|
+
"@lde/resolver": "^0.2.1",
|
|
37
|
+
"@rdfjs/types": "^2.0.1",
|
|
38
|
+
"rdf-data-factory": "^2.1.0",
|
|
39
|
+
"tslib": "^2.3.0"
|
|
40
|
+
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"@lde/pipeline": "^0.36.7",
|
|
43
|
+
"@lde/search": "^0.34.0"
|
|
44
|
+
},
|
|
45
|
+
"peerDependencies": {
|
|
46
|
+
"@lde/search": "^0.34.0"
|
|
47
|
+
},
|
|
48
|
+
"engines": {
|
|
49
|
+
"node": ">=24"
|
|
50
|
+
},
|
|
51
|
+
"publishConfig": {
|
|
52
|
+
"access": "public"
|
|
53
|
+
},
|
|
54
|
+
"nx": {
|
|
55
|
+
"name": "iiif-images"
|
|
56
|
+
}
|
|
6
57
|
}
|