ochre-sdk 1.0.12 → 1.0.14

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.
Files changed (52) hide show
  1. package/README.md +3 -1
  2. package/dist/constants.d.mts +17 -0
  3. package/dist/constants.mjs +85 -0
  4. package/dist/fetchers/gallery.d.mts +38 -0
  5. package/dist/fetchers/gallery.mjs +91 -0
  6. package/dist/fetchers/item-links.d.mts +32 -0
  7. package/dist/fetchers/item-links.mjs +120 -0
  8. package/dist/fetchers/item.d.mts +74 -0
  9. package/dist/fetchers/item.mjs +146 -0
  10. package/dist/fetchers/set/items.d.mts +48 -0
  11. package/dist/fetchers/set/items.mjs +268 -0
  12. package/dist/fetchers/set/property-values.d.mts +46 -0
  13. package/dist/fetchers/set/property-values.mjs +514 -0
  14. package/dist/fetchers/website.d.mts +25 -0
  15. package/dist/fetchers/website.mjs +38 -0
  16. package/dist/getters.d.mts +193 -0
  17. package/dist/getters.mjs +341 -0
  18. package/dist/helpers.d.mts +18 -0
  19. package/dist/helpers.mjs +33 -0
  20. package/dist/index.d.mts +12 -1966
  21. package/dist/index.mjs +9 -7201
  22. package/dist/parsers/helpers.d.mts +27 -0
  23. package/dist/parsers/helpers.mjs +53 -0
  24. package/dist/parsers/index.d.mts +65 -0
  25. package/dist/parsers/index.mjs +1338 -0
  26. package/dist/parsers/mdx.d.mts +4 -0
  27. package/dist/parsers/mdx.mjs +9 -0
  28. package/dist/parsers/multilingual.d.mts +189 -0
  29. package/dist/parsers/multilingual.mjs +410 -0
  30. package/dist/parsers/string.d.mts +29 -0
  31. package/dist/parsers/string.mjs +477 -0
  32. package/dist/parsers/website/index.d.mts +20 -0
  33. package/dist/parsers/website/index.mjs +1245 -0
  34. package/dist/parsers/website/reader.d.mts +29 -0
  35. package/dist/parsers/website/reader.mjs +75 -0
  36. package/dist/query.d.mts +13 -0
  37. package/dist/query.mjs +827 -0
  38. package/dist/schemas.d.mts +84 -0
  39. package/dist/schemas.mjs +232 -0
  40. package/dist/types/index.d.mts +840 -0
  41. package/dist/types/index.mjs +1 -0
  42. package/dist/types/website.d.mts +501 -0
  43. package/dist/types/website.mjs +1 -0
  44. package/dist/utils.d.mts +34 -0
  45. package/dist/utils.mjs +172 -0
  46. package/dist/xml/metadata.d.mts +5 -0
  47. package/dist/xml/metadata.mjs +30 -0
  48. package/dist/xml/schemas.d.mts +13 -0
  49. package/dist/xml/schemas.mjs +849 -0
  50. package/dist/xml/types.d.mts +901 -0
  51. package/dist/xml/types.mjs +1 -0
  52. package/package.json +19 -17
package/README.md CHANGED
@@ -47,7 +47,9 @@ present and `error` is `null`; on failure, the parsed value is `null` and
47
47
 
48
48
  - `fetchItem(uuid, options)` fetches and parses a single OCHRE item. Passing
49
49
  `category` narrows the returned TypeScript type, and `containedItemCategory`
50
- controls how nested Tree or Set contents are parsed.
50
+ controls how nested Tree or Set contents are parsed. For large Trees or Sets,
51
+ pass `shouldOmitEmbeddedItems: true` to fetch the top-level item without its
52
+ embedded `<items>` payload.
51
53
  - `fetchItemLinks(uuid, options)` fetches items linked from a source item and
52
54
  parses them as embedded OCHRE items.
53
55
  - `fetchGallery(params, options)` fetches paginated resource galleries with an
@@ -0,0 +1,17 @@
1
+ import { X2jOptions } from "fast-xml-parser";
2
+
3
+ //#region src/constants.d.ts
4
+ declare const XML_ARRAY_TAGS: ReadonlyArray<string>;
5
+ declare const XML_PARSER_OPTIONS: X2jOptions;
6
+ declare const DEFAULT_LANGUAGES: readonly ["eng"];
7
+ declare const BELONGS_TO_COLLECTION_UUID = "30054cb2-909a-4f34-8db9-8fe7369d691d";
8
+ declare const PRESENTATION_ITEM_UUID = "f1c131b6-1498-48a4-95bf-a9edae9fd518";
9
+ declare const TEXT_ANNOTATION_UUID = "b9ca2732-78f4-416e-b77f-dae7647e68a9";
10
+ declare const TEXT_ANNOTATION_HOVER_CARD_UUID = "c7f6a08a-f07b-49b6-bcb1-af485da3c58f";
11
+ declare const TEXT_ANNOTATION_ITEM_PAGE_VARIANT_UUID = "bf4476ab-6bc8-40d0-a001-1446213c72ce";
12
+ declare const TEXT_ANNOTATION_ENTRY_PAGE_VARIANT_UUID = "9d52db95-a9cf-45f7-a0bf-fc9ba9f0aae0";
13
+ declare const TEXT_ANNOTATION_TEXT_STYLING_UUID = "3e6f86ab-df81-45ae-8257-e2867357df56";
14
+ declare const TEXT_ANNOTATION_TEXT_STYLING_VARIANT_UUID = "e1647bef-d801-4100-bdde-d081c422f763";
15
+ declare const TEXT_ANNOTATION_TEXT_STYLING_HEADING_LEVEL_UUID = "d4266f0b-3f8d-4b32-8c15-4b229c8bb11e";
16
+ //#endregion
17
+ export { BELONGS_TO_COLLECTION_UUID, DEFAULT_LANGUAGES, PRESENTATION_ITEM_UUID, TEXT_ANNOTATION_ENTRY_PAGE_VARIANT_UUID, TEXT_ANNOTATION_HOVER_CARD_UUID, TEXT_ANNOTATION_ITEM_PAGE_VARIANT_UUID, TEXT_ANNOTATION_TEXT_STYLING_HEADING_LEVEL_UUID, TEXT_ANNOTATION_TEXT_STYLING_UUID, TEXT_ANNOTATION_TEXT_STYLING_VARIANT_UUID, TEXT_ANNOTATION_UUID, XML_ARRAY_TAGS, XML_PARSER_OPTIONS };
@@ -0,0 +1,85 @@
1
+ //#region src/constants.ts
2
+ const XML_ARRAY_TAGS = [
3
+ "string",
4
+ "content",
5
+ "tree",
6
+ "bibliography",
7
+ "spatialUnit",
8
+ "concept",
9
+ "person",
10
+ "period",
11
+ "propertyValue",
12
+ "propertyVariable",
13
+ "resource",
14
+ "text",
15
+ "set",
16
+ "dictionaryUnit",
17
+ "variable",
18
+ "property",
19
+ "value",
20
+ "context",
21
+ "creator",
22
+ "author",
23
+ "event",
24
+ "interpretation",
25
+ "observation",
26
+ "observer",
27
+ "heading",
28
+ "note",
29
+ "reference",
30
+ "coord",
31
+ "area",
32
+ "footnote",
33
+ "language",
34
+ "section",
35
+ "translation",
36
+ "phonemic",
37
+ "editor",
38
+ "publisher",
39
+ "scope",
40
+ "level",
41
+ "style",
42
+ "flattenContexts",
43
+ "suppressContexts",
44
+ "filterContexts",
45
+ "sortContexts",
46
+ "detailContexts",
47
+ "downloadContexts",
48
+ "labelContexts",
49
+ "prominentContexts"
50
+ ];
51
+ const XML_PARSER_OPTIONS = {
52
+ alwaysCreateTextNode: true,
53
+ captureMetaData: true,
54
+ ignoreAttributes: false,
55
+ removeNSPrefix: true,
56
+ ignorePiTags: true,
57
+ trimValues: false,
58
+ parseTagValue: false,
59
+ parseAttributeValue: false,
60
+ attributeNamePrefix: "",
61
+ textNodeName: "payload",
62
+ stopNodes: ["*.referenceFormatDiv", "*.citationFormatSpan"],
63
+ htmlEntities: true,
64
+ isArray(tagName, jPath, isLeafNode, isAttribute) {
65
+ if (isAttribute) return false;
66
+ if (XML_ARRAY_TAGS.includes(tagName)) return true;
67
+ return false;
68
+ },
69
+ attributeValueProcessor: (attrName, attrValue) => {
70
+ if (attrValue.startsWith("xs:")) return attrValue.replace("xs:", "");
71
+ return null;
72
+ }
73
+ };
74
+ const DEFAULT_LANGUAGES = ["eng"];
75
+ const BELONGS_TO_COLLECTION_UUID = "30054cb2-909a-4f34-8db9-8fe7369d691d";
76
+ const PRESENTATION_ITEM_UUID = "f1c131b6-1498-48a4-95bf-a9edae9fd518";
77
+ const TEXT_ANNOTATION_UUID = "b9ca2732-78f4-416e-b77f-dae7647e68a9";
78
+ const TEXT_ANNOTATION_HOVER_CARD_UUID = "c7f6a08a-f07b-49b6-bcb1-af485da3c58f";
79
+ const TEXT_ANNOTATION_ITEM_PAGE_VARIANT_UUID = "bf4476ab-6bc8-40d0-a001-1446213c72ce";
80
+ const TEXT_ANNOTATION_ENTRY_PAGE_VARIANT_UUID = "9d52db95-a9cf-45f7-a0bf-fc9ba9f0aae0";
81
+ const TEXT_ANNOTATION_TEXT_STYLING_UUID = "3e6f86ab-df81-45ae-8257-e2867357df56";
82
+ const TEXT_ANNOTATION_TEXT_STYLING_VARIANT_UUID = "e1647bef-d801-4100-bdde-d081c422f763";
83
+ const TEXT_ANNOTATION_TEXT_STYLING_HEADING_LEVEL_UUID = "d4266f0b-3f8d-4b32-8c15-4b229c8bb11e";
84
+ //#endregion
85
+ export { BELONGS_TO_COLLECTION_UUID, DEFAULT_LANGUAGES, PRESENTATION_ITEM_UUID, TEXT_ANNOTATION_ENTRY_PAGE_VARIANT_UUID, TEXT_ANNOTATION_HOVER_CARD_UUID, TEXT_ANNOTATION_ITEM_PAGE_VARIANT_UUID, TEXT_ANNOTATION_TEXT_STYLING_HEADING_LEVEL_UUID, TEXT_ANNOTATION_TEXT_STYLING_UUID, TEXT_ANNOTATION_TEXT_STYLING_VARIANT_UUID, TEXT_ANNOTATION_UUID, XML_ARRAY_TAGS, XML_PARSER_OPTIONS };
@@ -0,0 +1,38 @@
1
+ import { Gallery } from "../types/index.mjs";
2
+
3
+ //#region src/fetchers/gallery.d.ts
4
+ type FetchFunction = (input: string | URL | globalThis.Request, init?: RequestInit) => Promise<Response>;
5
+ type FetchGalleryBaseOptions<TLanguages extends ReadonlyArray<string> | undefined = undefined> = {
6
+ languages?: TLanguages;
7
+ fetch?: FetchFunction;
8
+ };
9
+ type FetchGalleryLanguages<TLanguages extends ReadonlyArray<string> | undefined> = TLanguages extends readonly [] ? ReadonlyArray<string> : TLanguages extends ReadonlyArray<string> ? TLanguages : ReadonlyArray<string>;
10
+ /**
11
+ * Fetches and parses a gallery from the OCHRE API
12
+ *
13
+ * @param params - The parameters for the fetch
14
+ * @param params.uuid - The UUID of the gallery
15
+ * @param params.filter - The filter to apply to the gallery
16
+ * @param params.page - The page number to fetch
17
+ * @param params.perPage - The number of items per page
18
+ * @param options - The options for the fetch
19
+ * @param options.languages - Language codes to parse. Inline arrays preserve literal types automatically.
20
+ * @param options.fetch - The fetch function to use
21
+ * @returns The parsed gallery or an error message if the fetch/parse fails
22
+ */
23
+ declare function fetchGallery<const TLanguages extends ReadonlyArray<string> | undefined = undefined>(params: {
24
+ uuid: string;
25
+ filter?: string;
26
+ page: number;
27
+ perPage: number;
28
+ }, options?: FetchGalleryBaseOptions<TLanguages>): Promise<{
29
+ gallery: Gallery<FetchGalleryLanguages<TLanguages>>;
30
+ error: null;
31
+ detailedError: null;
32
+ } | {
33
+ gallery: null;
34
+ error: string;
35
+ detailedError: string;
36
+ }>;
37
+ //#endregion
38
+ export { fetchGallery };
@@ -0,0 +1,91 @@
1
+ import { DEFAULT_LANGUAGES, XML_PARSER_OPTIONS } from "../constants.mjs";
2
+ import { createSchemaValidationError, getErrorOutput, stringLiteral } from "../utils.mjs";
3
+ import { gallerySchema, iso639_3Schema } from "../schemas.mjs";
4
+ import { restoreXMLMetadata } from "../xml/metadata.mjs";
5
+ import { parseGallery } from "../parsers/index.mjs";
6
+ import { XMLGalleryData } from "../xml/schemas.mjs";
7
+ import * as v from "valibot";
8
+ import { XMLParser } from "fast-xml-parser";
9
+ //#region src/fetchers/gallery.ts
10
+ function parseLanguages(languages) {
11
+ const parsedLanguages = [];
12
+ for (const language of languages) parsedLanguages.push(v.parse(iso639_3Schema, language));
13
+ return parsedLanguages;
14
+ }
15
+ function isRecord(value) {
16
+ return typeof value === "object" && value !== null;
17
+ }
18
+ function collectContentLanguages(value, languages) {
19
+ if (Array.isArray(value)) {
20
+ for (const item of value) collectContentLanguages(item, languages);
21
+ return;
22
+ }
23
+ if (!isRecord(value)) return;
24
+ const content = value.content;
25
+ if (Array.isArray(content)) for (const contentItem of content) {
26
+ if (!isRecord(contentItem)) continue;
27
+ const language = contentItem.lang;
28
+ if (typeof language === "string" && language !== "zxx") languages.add(language);
29
+ }
30
+ for (const child of Object.values(value)) collectContentLanguages(child, languages);
31
+ }
32
+ function resolveGalleryLanguages(data, requestedLanguages) {
33
+ if (requestedLanguages.length > 0) return requestedLanguages;
34
+ const languages = /* @__PURE__ */ new Set();
35
+ collectContentLanguages(data.result.ochre.gallery, languages);
36
+ return languages.size > 0 ? [...languages] : [...DEFAULT_LANGUAGES];
37
+ }
38
+ function buildXQuery(params) {
39
+ const { uuid, filter, page, perPage } = params;
40
+ const start = (page - 1) * perPage + 1;
41
+ const filterLiteral = stringLiteral(filter?.trim() ?? "");
42
+ return `<ochre>{
43
+ for $q in doc()/ochre[@uuid=${stringLiteral(uuid)}]
44
+ let $filter := ${filterLiteral}
45
+ let $resources := $q//items/resource
46
+ let $filtered :=
47
+ if ($filter = "")
48
+ then $resources
49
+ else $resources[contains(lower-case(string-join(identification/label//text(), "")), lower-case($filter))]
50
+ let $maxLength := count($filtered)
51
+ return <gallery maxLength="{$maxLength}">{
52
+ $q/metadata/project,
53
+ $q/metadata/item,
54
+ subsequence($filtered, ${start}, ${perPage})
55
+ }</gallery>
56
+ }</ochre>`;
57
+ }
58
+ async function fetchGallery(params, options) {
59
+ try {
60
+ const { uuid, filter, page, perPage } = v.parse(gallerySchema, params);
61
+ const requestedLanguages = options?.languages == null ? [] : parseLanguages(options.languages);
62
+ const response = await (options?.fetch ?? fetch)("https://ochre.lib.uchicago.edu/ochre/v2/ochre.php?xquery&xsl=none&lang=\"*\"", {
63
+ method: "POST",
64
+ body: buildXQuery({
65
+ uuid,
66
+ filter,
67
+ page,
68
+ perPage
69
+ }),
70
+ headers: { "Content-Type": "application/xquery" }
71
+ });
72
+ if (!response.ok) throw new Error("Error fetching gallery items, please try again later.", { cause: response.statusText });
73
+ const dataRaw = await response.text();
74
+ const data = new XMLParser(XML_PARSER_OPTIONS).parse(dataRaw);
75
+ const { success, issues, output } = v.safeParse(XMLGalleryData, data);
76
+ if (!success) throw createSchemaValidationError("Failed to parse gallery XML", issues);
77
+ restoreXMLMetadata(output, data);
78
+ return {
79
+ gallery: parseGallery(output, { languages: resolveGalleryLanguages(output, requestedLanguages) }),
80
+ error: null,
81
+ detailedError: null
82
+ };
83
+ } catch (error) {
84
+ return {
85
+ gallery: null,
86
+ ...getErrorOutput(error, "Failed to fetch gallery")
87
+ };
88
+ }
89
+ }
90
+ //#endregion
91
+ export { fetchGallery };
@@ -0,0 +1,32 @@
1
+ import { ContainedItemCategoryFromOption, ContainedItemCategoryOption, Item, ItemCategory, ItemContainerCategory } from "../types/index.mjs";
2
+
3
+ //#region src/fetchers/item-links.d.ts
4
+ type FetchFunction = (input: string | URL | globalThis.Request, init?: RequestInit) => Promise<Response>;
5
+ type FetchItemLinksBaseOptions<TLanguages extends ReadonlyArray<string> | undefined = undefined> = {
6
+ languages?: TLanguages;
7
+ fetch?: FetchFunction;
8
+ };
9
+ type FetchItemLinksLanguages<TLanguages extends ReadonlyArray<string> | undefined> = TLanguages extends readonly [] ? ReadonlyArray<string> : TLanguages extends ReadonlyArray<string> ? TLanguages : ReadonlyArray<string>;
10
+ /**
11
+ * Fetches linked OCHRE items by source-item UUID.
12
+ *
13
+ * @param uuid - The UUID of the OCHRE item whose linked items should be fetched
14
+ * @param options - Fetch and parser options
15
+ * @param options.containedItemCategory - The category of items inside linked Trees/Sets to parse. Tree accepts one category; Set accepts one category or an array.
16
+ * @param options.languages - Language codes to parse. Inline arrays preserve literal types automatically.
17
+ * @param options.fetch - Custom fetch function to use instead of the default fetch
18
+ * @returns An object containing parsed linked items
19
+ */
20
+ declare function fetchItemLinks<const TContainedItemCategory extends ContainedItemCategoryOption<ItemContainerCategory> | undefined = undefined, const TLanguages extends ReadonlyArray<string> | undefined = undefined>(uuid: string, options?: FetchItemLinksBaseOptions<TLanguages> & {
21
+ containedItemCategory?: TContainedItemCategory;
22
+ }): Promise<{
23
+ items: Array<Item<ItemCategory, ContainedItemCategoryFromOption<ItemCategory, TContainedItemCategory>, FetchItemLinksLanguages<TLanguages>, "embedded">>;
24
+ error: null;
25
+ detailedError: null;
26
+ } | {
27
+ items: null;
28
+ error: string;
29
+ detailedError: string;
30
+ }>;
31
+ //#endregion
32
+ export { fetchItemLinks };
@@ -0,0 +1,120 @@
1
+ import { DEFAULT_LANGUAGES, XML_PARSER_OPTIONS } from "../constants.mjs";
2
+ import { createSchemaValidationError, getErrorOutput } from "../utils.mjs";
3
+ import { iso639_3Schema, uuidSchema } from "../schemas.mjs";
4
+ import { restoreXMLMetadata } from "../xml/metadata.mjs";
5
+ import { parseLinkedItems } from "../parsers/index.mjs";
6
+ import { XMLItemLinksData } from "../xml/schemas.mjs";
7
+ import * as v from "valibot";
8
+ import { XMLParser } from "fast-xml-parser";
9
+ //#region src/fetchers/item-links.ts
10
+ function parseLanguages(languages) {
11
+ const parsedLanguages = [];
12
+ for (const language of languages) parsedLanguages.push(v.parse(iso639_3Schema, language));
13
+ return parsedLanguages;
14
+ }
15
+ function isRecord(value) {
16
+ return typeof value === "object" && value !== null;
17
+ }
18
+ function collectContentLanguages(value, languages) {
19
+ if (Array.isArray(value)) {
20
+ for (const item of value) collectContentLanguages(item, languages);
21
+ return;
22
+ }
23
+ if (!isRecord(value)) return;
24
+ const content = value.content;
25
+ if (Array.isArray(content)) for (const contentItem of content) {
26
+ if (!isRecord(contentItem)) continue;
27
+ const language = contentItem.lang;
28
+ if (typeof language === "string" && language !== "zxx") languages.add(language);
29
+ }
30
+ for (const child of Object.values(value)) collectContentLanguages(child, languages);
31
+ }
32
+ function resolveItemLinksLanguages(data, requestedLanguages) {
33
+ if (requestedLanguages.length > 0) return requestedLanguages;
34
+ const languages = /* @__PURE__ */ new Set();
35
+ collectContentLanguages(data.result.ochre.items, languages);
36
+ return languages.size > 0 ? [...languages] : [...DEFAULT_LANGUAGES];
37
+ }
38
+ /**
39
+ * Build an XQuery string to fetch linked items from the OCHRE API.
40
+ *
41
+ * @param uuid - The UUID of the OCHRE item whose links should be fetched
42
+ * @returns An XQuery string
43
+ */
44
+ function buildXQuery(uuid) {
45
+ return `<ochre>{${`let $item-uuid := "${uuid}"
46
+
47
+ let $source-items := (
48
+ fn:collection("ochre/resource")/ochre[@uuid = $item-uuid]/resource,
49
+ fn:collection("ochre/bibliography")/ochre[@uuid = $item-uuid]/bibliography,
50
+ fn:collection("ochre/period")/ochre[@uuid = $item-uuid]/period,
51
+ fn:collection("ochre/person")/ochre[@uuid = $item-uuid]/person,
52
+ fn:collection("ochre/propertyVariable")/ochre[@uuid = $item-uuid]/propertyVariable,
53
+ fn:collection("ochre/propertyValue")/ochre[@uuid = $item-uuid]/propertyValue,
54
+ fn:collection("ochre/text")/ochre[@uuid = $item-uuid]/text,
55
+ fn:collection("ochre/tree")/ochre[@uuid = $item-uuid]/tree,
56
+ fn:collection("ochre/set")/ochre[@uuid = $item-uuid]/set,
57
+ fn:collection("ochre/spatialUnit")/ochre[@uuid = $item-uuid]/spatialUnit,
58
+ fn:collection("ochre/concept")/ochre[@uuid = $item-uuid]/concept
59
+ )
60
+
61
+ let $link-nodes := (
62
+ $source-items/links/*,
63
+ $source-items/observations/observation/links/*,
64
+ $source-items/interpretations/interpretation/links/*
65
+ )
66
+
67
+ return
68
+ <items>{
69
+ for $link at $position in $link-nodes
70
+ let $uuid := $link/@uuid/string()
71
+ let $category := name($link)
72
+ where $uuid ne "" and not($uuid = $link-nodes[position() lt $position]/@uuid/string())
73
+ return
74
+ if ($category = "resource") then fn:collection("ochre/resource")/ochre/resource[@uuid = $uuid]
75
+ else if ($category = "bibliography") then fn:collection("ochre/bibliography")/ochre/bibliography[@uuid = $uuid]
76
+ else if ($category = "period") then fn:collection("ochre/period")/ochre/period[@uuid = $uuid]
77
+ else if ($category = "person") then fn:collection("ochre/person")/ochre/person[@uuid = $uuid]
78
+ else if ($category = "propertyVariable" or $category = "variable") then fn:collection("ochre/propertyVariable")/ochre/propertyVariable[@uuid = $uuid]
79
+ else if ($category = "propertyValue" or $category = "value") then fn:collection("ochre/propertyValue")/ochre/propertyValue[@uuid = $uuid]
80
+ else if ($category = "text") then fn:collection("ochre/text")/ochre/text[@uuid = $uuid]
81
+ else if ($category = "tree") then fn:collection("ochre/tree")/ochre/tree[@uuid = $uuid]
82
+ else if ($category = "set") then fn:collection("ochre/set")/ochre/set[@uuid = $uuid]
83
+ else if ($category = "spatialUnit") then fn:collection("ochre/spatialUnit")/ochre/spatialUnit[@uuid = $uuid]
84
+ else if ($category = "concept") then fn:collection("ochre/concept")/ochre/concept[@uuid = $uuid]
85
+ else ()
86
+ }</items>`}}</ochre>`;
87
+ }
88
+ async function fetchItemLinks(uuid, options) {
89
+ try {
90
+ const parsedUuid = v.parse(uuidSchema, uuid);
91
+ const requestedLanguages = options?.languages == null ? [] : parseLanguages(options.languages);
92
+ const response = await (options?.fetch ?? fetch)("https://ochre.lib.uchicago.edu/ochre/v2/ochre.php?xquery&xsl=none&lang=\"*\"", {
93
+ method: "POST",
94
+ body: buildXQuery(parsedUuid),
95
+ headers: { "Content-Type": "application/xquery" }
96
+ });
97
+ if (!response.ok) throw new Error("Failed to fetch OCHRE item links", { cause: response.statusText });
98
+ const dataRaw = await response.text();
99
+ const data = new XMLParser(XML_PARSER_OPTIONS).parse(dataRaw);
100
+ const { success, issues, output } = v.safeParse(XMLItemLinksData, data);
101
+ if (!success) throw createSchemaValidationError("Failed to parse OCHRE item links", issues);
102
+ restoreXMLMetadata(output, data);
103
+ const languages = resolveItemLinksLanguages(output, requestedLanguages);
104
+ return {
105
+ items: parseLinkedItems(output.result.ochre.items, {
106
+ containedItemCategory: options?.containedItemCategory,
107
+ languages
108
+ }),
109
+ error: null,
110
+ detailedError: null
111
+ };
112
+ } catch (error) {
113
+ return {
114
+ items: null,
115
+ ...getErrorOutput(error, "Unknown error")
116
+ };
117
+ }
118
+ }
119
+ //#endregion
120
+ export { fetchItemLinks };
@@ -0,0 +1,74 @@
1
+ import { ContainedItemCategory, ContainedItemCategoryFromOption, ContainedItemCategoryOption, Item, ItemCategory, ItemContainerCategory, ItemWithoutEmbeddedItems } from "../types/index.mjs";
2
+
3
+ //#region src/fetchers/item.d.ts
4
+ type FetchFunction = (input: string | URL | globalThis.Request, init?: RequestInit) => Promise<Response>;
5
+ type FetchItemBaseOptions<TLanguages extends ReadonlyArray<string> | undefined = undefined> = {
6
+ languages?: TLanguages;
7
+ fetch?: FetchFunction;
8
+ };
9
+ type FetchItemNoOmitEmbeddedItemsOption = {
10
+ shouldOmitEmbeddedItems?: false;
11
+ };
12
+ type FetchItemLanguages<TLanguages extends ReadonlyArray<string> | undefined> = TLanguages extends readonly [] ? ReadonlyArray<string> : TLanguages extends ReadonlyArray<string> ? TLanguages : ReadonlyArray<string>;
13
+ type FetchItemSuccess<TItem> = {
14
+ item: TItem;
15
+ error: null;
16
+ detailedError: null;
17
+ };
18
+ type FetchItemError = {
19
+ item: null;
20
+ error: string;
21
+ detailedError: string;
22
+ };
23
+ type FetchItemResult<TItem> = Promise<FetchItemSuccess<TItem> | FetchItemError>;
24
+ /**
25
+ * Defines a reusable languages tuple with validation and literal type inference.
26
+ *
27
+ * Inline arrays can be passed directly to fetchItem:
28
+ * `fetchItem(uuid, { languages: ["eng", "spa"] })`.
29
+ *
30
+ * Use this helper when the language set is stored separately:
31
+ * `const languages = defineLanguages("eng", "spa")`.
32
+ */
33
+ declare function defineLanguages<const TLanguages extends ReadonlyArray<string>>(...languages: TLanguages): TLanguages;
34
+ /**
35
+ * @deprecated Pass inline language arrays directly to fetchItem, or use
36
+ * defineLanguages("eng", "spa") for reusable language tuples.
37
+ */
38
+ declare function withLanguages<const TLanguages extends ReadonlyArray<string>>(languages: TLanguages): TLanguages;
39
+ /**
40
+ * Fetches an OCHRE item by UUID from the OCHRE API
41
+ *
42
+ * @param uuid - The UUID of the OCHRE item to fetch
43
+ * @param options - Required options object
44
+ * @param options.category - The category of the OCHRE item to fetch
45
+ * @param options.containedItemCategory - The category of items inside the OCHRE item to fetch. Only valid for Trees and Sets. Tree accepts one category; Set accepts one category or an array.
46
+ * @param options.shouldOmitEmbeddedItems - Whether to omit the embedded `<items>` node when fetching a Tree or Set.
47
+ * @param options.languages - Language codes to parse. Inline arrays preserve literal types automatically.
48
+ * @param options.fetch - Custom fetch function to use instead of the default fetch
49
+ * @returns An object containing the parsed item
50
+ */
51
+ declare function fetchItem<const TContainedItemCategory extends ContainedItemCategoryOption<ItemContainerCategory> | undefined = undefined, const TLanguages extends ReadonlyArray<string> | undefined = undefined>(uuid: string, options?: FetchItemBaseOptions<TLanguages> & FetchItemNoOmitEmbeddedItemsOption & {
52
+ category?: undefined;
53
+ containedItemCategory?: TContainedItemCategory;
54
+ }): FetchItemResult<Item<ItemCategory, ContainedItemCategoryFromOption<ItemCategory, TContainedItemCategory>, FetchItemLanguages<TLanguages>>>;
55
+ declare function fetchItem<const TContainedItemCategory extends ContainedItemCategoryOption<ItemContainerCategory> | undefined = undefined, const TLanguages extends ReadonlyArray<string> | undefined = undefined>(uuid: string, options: FetchItemBaseOptions<TLanguages> & {
56
+ category?: undefined;
57
+ containedItemCategory?: TContainedItemCategory;
58
+ shouldOmitEmbeddedItems: true;
59
+ }): FetchItemResult<ItemWithoutEmbeddedItems<ItemContainerCategory, ContainedItemCategoryFromOption<ItemContainerCategory, TContainedItemCategory>, FetchItemLanguages<TLanguages>>>;
60
+ declare function fetchItem<const TCategory extends ItemContainerCategory, const TContainedItemCategory extends ContainedItemCategoryOption<TCategory> | undefined = undefined, const TLanguages extends ReadonlyArray<string> | undefined = undefined>(uuid: string, options: FetchItemBaseOptions<TLanguages> & FetchItemNoOmitEmbeddedItemsOption & {
61
+ category: TCategory;
62
+ containedItemCategory?: TContainedItemCategory;
63
+ }): FetchItemResult<Item<TCategory, ContainedItemCategoryFromOption<TCategory, TContainedItemCategory>, FetchItemLanguages<TLanguages>>>;
64
+ declare function fetchItem<const TCategory extends ItemContainerCategory, const TContainedItemCategory extends ContainedItemCategoryOption<TCategory> | undefined = undefined, const TLanguages extends ReadonlyArray<string> | undefined = undefined>(uuid: string, options: FetchItemBaseOptions<TLanguages> & {
65
+ category: TCategory;
66
+ containedItemCategory?: TContainedItemCategory;
67
+ shouldOmitEmbeddedItems: true;
68
+ }): FetchItemResult<ItemWithoutEmbeddedItems<TCategory, ContainedItemCategoryFromOption<TCategory, TContainedItemCategory>, FetchItemLanguages<TLanguages>>>;
69
+ declare function fetchItem<const TCategory extends ItemCategory, const TLanguages extends ReadonlyArray<string> | undefined = undefined>(uuid: string, options: FetchItemBaseOptions<TLanguages> & FetchItemNoOmitEmbeddedItemsOption & {
70
+ category: TCategory;
71
+ containedItemCategory?: never;
72
+ }): FetchItemResult<Item<TCategory, ContainedItemCategory<TCategory>, FetchItemLanguages<TLanguages>>>;
73
+ //#endregion
74
+ export { defineLanguages, fetchItem, withLanguages };
@@ -0,0 +1,146 @@
1
+ import { XML_PARSER_OPTIONS } from "../constants.mjs";
2
+ import { createSchemaValidationError, getErrorOutput, stringLiteral } from "../utils.mjs";
3
+ import { iso639_3Schema, uuidSchema } from "../schemas.mjs";
4
+ import { restoreXMLMetadata } from "../xml/metadata.mjs";
5
+ import { parseItem } from "../parsers/index.mjs";
6
+ import { XMLData } from "../xml/schemas.mjs";
7
+ import { parseWebpageView } from "../parsers/website/index.mjs";
8
+ import * as v from "valibot";
9
+ import { XMLParser } from "fast-xml-parser";
10
+ //#region src/fetchers/item.ts
11
+ function isItemContainerCategory(category) {
12
+ return category === "tree" || category === "set";
13
+ }
14
+ function isItemContainer(item) {
15
+ return isItemContainerCategory(item.category);
16
+ }
17
+ function assertItemCategoryAllowed(category, containedItemCategory) {
18
+ if (category == null || containedItemCategory == null || isItemContainerCategory(category)) return;
19
+ throw new Error(`containedItemCategory can only be used when category is "tree" or "set"; received category "${category}"`);
20
+ }
21
+ function assertShouldOmitEmbeddedItemsAllowed(category, shouldOmitEmbeddedItems) {
22
+ if (!shouldOmitEmbeddedItems || category == null || isItemContainerCategory(category)) return;
23
+ throw new Error(`shouldOmitEmbeddedItems can only be used when category is "tree" or "set"; received category "${category}"`);
24
+ }
25
+ function normalizeFetchedCategory(category) {
26
+ switch (category) {
27
+ case "tree":
28
+ case "bibliography":
29
+ case "concept":
30
+ case "spatialUnit":
31
+ case "period":
32
+ case "person":
33
+ case "propertyVariable":
34
+ case "propertyValue":
35
+ case "resource":
36
+ case "text":
37
+ case "set": return category;
38
+ case "variable": return "propertyVariable";
39
+ case "value": return "propertyValue";
40
+ default: return null;
41
+ }
42
+ }
43
+ function inferFetchItemCategory(rawOchre) {
44
+ const metadataCategory = normalizeFetchedCategory(rawOchre.metadata.item?.category);
45
+ if (metadataCategory != null) return metadataCategory;
46
+ if ("tree" in rawOchre) return "tree";
47
+ if ("bibliography" in rawOchre) return "bibliography";
48
+ if ("concept" in rawOchre) return "concept";
49
+ if ("spatialUnit" in rawOchre) return "spatialUnit";
50
+ if ("period" in rawOchre) return "period";
51
+ if ("person" in rawOchre) return "person";
52
+ if ("propertyVariable" in rawOchre || "variable" in rawOchre) return "propertyVariable";
53
+ if ("propertyValue" in rawOchre || "value" in rawOchre) return "propertyValue";
54
+ if ("resource" in rawOchre) return "resource";
55
+ if ("text" in rawOchre) return "text";
56
+ if ("set" in rawOchre) return "set";
57
+ throw new Error("Could not infer OCHRE item category", { cause: rawOchre });
58
+ }
59
+ function buildOmitEmbeddedItemsXQuery(uuid) {
60
+ return `xquery version "1.0-ml";
61
+
62
+ let $ochre := doc()/ochre[@uuid=${stringLiteral(uuid)}][1]
63
+ return
64
+ if (empty($ochre)) then ()
65
+ else element ochre {
66
+ $ochre/@*,
67
+ for $node in $ochre/node()
68
+ return
69
+ if (local-name($node) = ("tree", "set"))
70
+ then element { node-name($node) } { $node/@*, $node/node()[not(self::items)] }
71
+ else $node
72
+ }`;
73
+ }
74
+ function omitEmbeddedItems(item) {
75
+ const { items: _items, ...itemWithoutEmbeddedItems } = item;
76
+ return itemWithoutEmbeddedItems;
77
+ }
78
+ /**
79
+ * Validate language codes while preserving literal tuple inference.
80
+ */
81
+ function parseLanguages(languages) {
82
+ const parsedLanguages = [];
83
+ for (const language of languages) parsedLanguages.push(v.parse(iso639_3Schema, language));
84
+ return parsedLanguages;
85
+ }
86
+ /**
87
+ * Defines a reusable languages tuple with validation and literal type inference.
88
+ *
89
+ * Inline arrays can be passed directly to fetchItem:
90
+ * `fetchItem(uuid, { languages: ["eng", "spa"] })`.
91
+ *
92
+ * Use this helper when the language set is stored separately:
93
+ * `const languages = defineLanguages("eng", "spa")`.
94
+ */
95
+ function defineLanguages(...languages) {
96
+ return parseLanguages(languages);
97
+ }
98
+ /**
99
+ * @deprecated Pass inline language arrays directly to fetchItem, or use
100
+ * defineLanguages("eng", "spa") for reusable language tuples.
101
+ */
102
+ function withLanguages(languages) {
103
+ return parseLanguages(languages);
104
+ }
105
+ async function fetchItem(uuid, options) {
106
+ try {
107
+ const parsedUuid = v.parse(uuidSchema, uuid);
108
+ assertItemCategoryAllowed(options?.category, options?.containedItemCategory);
109
+ const shouldOmitEmbeddedItems = options?.shouldOmitEmbeddedItems === true;
110
+ assertShouldOmitEmbeddedItemsAllowed(options?.category, shouldOmitEmbeddedItems);
111
+ const languages = options?.languages == null ? [] : parseLanguages(options.languages);
112
+ const fetcher = options?.fetch ?? fetch;
113
+ const response = shouldOmitEmbeddedItems ? await fetcher("https://ochre.lib.uchicago.edu/ochre/v2/ochre.php?xquery&xsl=none&lang=\"*\"", {
114
+ method: "POST",
115
+ body: buildOmitEmbeddedItemsXQuery(parsedUuid),
116
+ headers: { "Content-Type": "application/xquery" }
117
+ }) : await fetcher(`https://ochre.lib.uchicago.edu/ochre/v2/ochre.php?uuid=${parsedUuid}&xsl=none&lang="*"`);
118
+ if (!response.ok) throw new Error("Failed to fetch OCHRE data", { cause: response.statusText });
119
+ const dataRaw = await response.text();
120
+ const data = new XMLParser(XML_PARSER_OPTIONS).parse(dataRaw);
121
+ const { success, issues, output } = v.safeParse(XMLData, data);
122
+ if (!success) throw createSchemaValidationError("Failed to parse OCHRE data", issues);
123
+ restoreXMLMetadata(output, data);
124
+ const category = options?.category ?? inferFetchItemCategory(output.result.ochre);
125
+ assertItemCategoryAllowed(category, options?.containedItemCategory);
126
+ assertShouldOmitEmbeddedItemsAllowed(category, shouldOmitEmbeddedItems);
127
+ const parsedItem = parseItem(output, {
128
+ category,
129
+ containedItemCategory: options?.containedItemCategory,
130
+ languages,
131
+ parseResourceView: (view, context) => parseWebpageView(view, { languages: context.metadata.languages }, context)
132
+ });
133
+ return {
134
+ item: shouldOmitEmbeddedItems && isItemContainer(parsedItem) ? omitEmbeddedItems(parsedItem) : parsedItem,
135
+ error: null,
136
+ detailedError: null
137
+ };
138
+ } catch (error) {
139
+ return {
140
+ item: null,
141
+ ...getErrorOutput(error, "Unknown error")
142
+ };
143
+ }
144
+ }
145
+ //#endregion
146
+ export { defineLanguages, fetchItem, withLanguages };