ochre-sdk 1.0.4 → 1.0.6

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 (3) hide show
  1. package/README.md +1 -1
  2. package/dist/index.mjs +118 -87
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -33,7 +33,7 @@ const result = await fetchItem("<item-uuid>", {
33
33
  });
34
34
 
35
35
  if (result.error != null) {
36
- throw new Error(result.error);
36
+ throw new Error("Failed to fetch item", { cause: result.error });
37
37
  }
38
38
 
39
39
  console.log(result.item.identification.label.getText("eng"));
package/dist/index.mjs CHANGED
@@ -1409,6 +1409,12 @@ function parseStringContent(value, options = FALLBACK_PARSER_OPTIONS) {
1409
1409
  }
1410
1410
  //#endregion
1411
1411
  //#region src/parsers/index.ts
1412
+ function isXMLContextGroup(context) {
1413
+ return "context" in context;
1414
+ }
1415
+ function isXMLContextItem(context) {
1416
+ return "project" in context;
1417
+ }
1412
1418
  const SET_ITEM_CATEGORIES = ["tree", ...[
1413
1419
  "bibliography",
1414
1420
  "concept",
@@ -1521,27 +1527,33 @@ function emptyContextItem() {
1521
1527
  }
1522
1528
  function parseContext(rawContext) {
1523
1529
  const nodes = [];
1524
- for (const rawContextOuterItem of rawContext) for (const rawContextItem of rawContextOuterItem.context) {
1525
- const node = {
1526
- tree: rawContextItem.tree[0] == null ? emptyContextItem() : parseContextItem$1(rawContextItem.tree[0]),
1527
- project: parseContextItem$1(rawContextItem.project),
1528
- heading: []
1529
- };
1530
- const rawContextValues = rawContextItem;
1531
- for (const heading of rawContextValues.heading ?? []) node.heading.push(parseContextItem$1(heading));
1532
- for (const { raw, parsed } of CONTEXT_CATEGORY_MAPPINGS) {
1533
- const contextValues = rawContextValues[raw] ?? [];
1534
- for (const contextValue of contextValues) {
1535
- const parsedItems = node[parsed] ?? [];
1536
- parsedItems.push(parseContextItem$1(contextValue));
1537
- node[parsed] = parsedItems;
1530
+ let displayPath = "";
1531
+ for (const rawContextOuterItem of rawContext) {
1532
+ if (!isXMLContextGroup(rawContextOuterItem)) continue;
1533
+ displayPath = displayPath || rawContextOuterItem.displayPath;
1534
+ for (const rawContextItem of rawContextOuterItem.context) {
1535
+ if (!isXMLContextItem(rawContextItem)) continue;
1536
+ const node = {
1537
+ tree: rawContextItem.tree[0] == null ? emptyContextItem() : parseContextItem$1(rawContextItem.tree[0]),
1538
+ project: parseContextItem$1(rawContextItem.project),
1539
+ heading: []
1540
+ };
1541
+ const rawContextValues = rawContextItem;
1542
+ for (const heading of rawContextValues.heading ?? []) node.heading.push(parseContextItem$1(heading));
1543
+ for (const { raw, parsed } of CONTEXT_CATEGORY_MAPPINGS) {
1544
+ const contextValues = rawContextValues[raw] ?? [];
1545
+ for (const contextValue of contextValues) {
1546
+ const parsedItems = node[parsed] ?? [];
1547
+ parsedItems.push(parseContextItem$1(contextValue));
1548
+ node[parsed] = parsedItems;
1549
+ }
1538
1550
  }
1551
+ nodes.push(node);
1539
1552
  }
1540
- nodes.push(node);
1541
1553
  }
1542
1554
  return {
1543
1555
  nodes,
1544
- displayPath: rawContext[0]?.displayPath ?? ""
1556
+ displayPath
1545
1557
  };
1546
1558
  }
1547
1559
  function parseEventReference(rawReference, options) {
@@ -1692,16 +1704,19 @@ function normalizeSetItemCategories(containedItemCategory) {
1692
1704
  return uniqueCategories;
1693
1705
  }
1694
1706
  function normalizeTreeItemCategory(containedItemCategory) {
1695
- if (containedItemCategory != null && typeof containedItemCategory !== "string") throw new Error("Tree containedItemCategory must be a single category");
1696
- if (containedItemCategory === "tree") throw new Error("Tree containedItemCategory cannot be \"tree\"");
1707
+ if (containedItemCategory != null && typeof containedItemCategory !== "string") throw new Error("Tree containedItemCategory must be a single category", { cause: containedItemCategory });
1708
+ if (containedItemCategory === "tree") throw new Error("Tree containedItemCategory cannot be \"tree\"", { cause: containedItemCategory });
1697
1709
  return containedItemCategory;
1698
1710
  }
1699
1711
  function resolveTreeItemCategory(rawTree, containedItemCategory) {
1700
1712
  const inferredCategories = inferItemCategories(rawTree.items);
1701
- if (inferredCategories.length > 1) throw new Error(`Expected Tree items to contain one category, received ${inferredCategories.join(", ")}`);
1713
+ if (inferredCategories.length > 1) throw new Error(`Expected Tree items to contain one category, received ${inferredCategories.join(", ")}`, { cause: inferredCategories });
1702
1714
  const inferredCategory = inferredCategories[0] ?? null;
1703
- if (inferredCategory === "tree") throw new Error("Tree items cannot contain category \"tree\"");
1704
- if (containedItemCategory != null && inferredCategory != null && containedItemCategory !== inferredCategory) throw new Error(`Tree containedItemCategory "${containedItemCategory}" does not match XML items category "${inferredCategory}"`);
1715
+ if (inferredCategory === "tree") throw new Error("Tree items cannot contain category \"tree\"", { cause: inferredCategory });
1716
+ if (containedItemCategory != null && inferredCategory != null && containedItemCategory !== inferredCategory) throw new Error(`Tree containedItemCategory "${containedItemCategory}" does not match XML items category "${inferredCategory}"`, { cause: {
1717
+ containedItemCategory,
1718
+ inferredCategory
1719
+ } });
1705
1720
  return containedItemCategory ?? inferredCategory;
1706
1721
  }
1707
1722
  function parseImage(rawImage, options) {
@@ -1849,12 +1864,13 @@ function parseNotes(rawNotes, options) {
1849
1864
  }
1850
1865
  function parsePropertyDataType(dataType) {
1851
1866
  if (dataType == null || dataType === "") return "string";
1852
- for (const propertyDataType of PROPERTY_DATA_TYPES) if (dataType === propertyDataType) return propertyDataType;
1853
- throw new Error(`Invalid property value data type: ${dataType}`);
1867
+ const normalizedDataType = dataType.startsWith("xs:") ? dataType.slice(3) : dataType;
1868
+ for (const propertyDataType of PROPERTY_DATA_TYPES) if (normalizedDataType === propertyDataType) return propertyDataType;
1869
+ throw new Error(`Invalid property value data type: ${dataType}`, { cause: dataType });
1854
1870
  }
1855
1871
  function parsePropertyValueContent(value, options) {
1856
1872
  const dataType = parsePropertyDataType(value.dataType);
1857
- const rawLabel = value.content == null ? null : parseRequiredContentLike(value, options);
1873
+ const rawLabel = value.content != null ? parseRequiredContentLike(value, options) : value.payload != null && value.payload !== "" ? multilingualFromText(value.payload, options) : null;
1858
1874
  const displayText = rawLabel?.getText() ?? value.payload ?? value.slug ?? "";
1859
1875
  const contentText = value.rawValue ?? value.payload ?? displayText;
1860
1876
  const common = {
@@ -2580,7 +2596,7 @@ function resolveLanguages(requestedLanguages, metadataLanguages) {
2580
2596
  if (requestedLanguages.length === 0) return metadataLanguages;
2581
2597
  const unsupportedLanguages = [];
2582
2598
  for (const requestedLanguage of requestedLanguages) if (!metadataLanguages.some((metadataLanguage) => metadataLanguage.toLocaleLowerCase("en-US") === requestedLanguage.toLocaleLowerCase("en-US"))) unsupportedLanguages.push(requestedLanguage);
2583
- if (unsupportedLanguages.length > 0) throw new Error(`The following language(s) are not supported by the dataset: ${unsupportedLanguages.toSorted((a, b) => a.localeCompare(b, "en-US")).join(", ")}. Available languages: ${metadataLanguages.toSorted((a, b) => a.localeCompare(b, "en-US")).join(", ")}`);
2599
+ if (unsupportedLanguages.length > 0) throw new Error(`The following language(s) are not supported by the dataset: ${unsupportedLanguages.toSorted((a, b) => a.localeCompare(b, "en-US")).join(", ")}. Available languages: ${metadataLanguages.toSorted((a, b) => a.localeCompare(b, "en-US")).join(", ")}`, { cause: unsupportedLanguages });
2584
2600
  return requestedLanguages;
2585
2601
  }
2586
2602
  function resolveDefaultLanguage(rawOchre, languages) {
@@ -2590,7 +2606,7 @@ function resolveDefaultLanguage(rawOchre, languages) {
2590
2606
  }
2591
2607
  for (const language of languages) if (language === DEFAULT_LANGUAGES[0]) return language;
2592
2608
  const firstLanguage = languages[0];
2593
- if (firstLanguage == null) throw new Error("Default language not found");
2609
+ if (firstLanguage == null) throw new Error("Default language not found", { cause: languages });
2594
2610
  return firstLanguage;
2595
2611
  }
2596
2612
  function parseMetadataPublisher(rawPublisher) {
@@ -2635,11 +2651,11 @@ function inferTopLevelCategory(rawOchre) {
2635
2651
  for (const category of SET_ITEM_CATEGORIES) if (category in rawOchre) return category;
2636
2652
  if ("variable" in rawOchre) return "propertyVariable";
2637
2653
  if ("value" in rawOchre) return "propertyValue";
2638
- throw new Error("Could not infer OCHRE item category");
2654
+ throw new Error("Could not infer OCHRE item category", { cause: rawOchre });
2639
2655
  }
2640
2656
  function getSingleTopLevelRawItem(items, category) {
2641
- if (items == null || items.length === 0) throw new Error(`${category} not found`);
2642
- if (items.length > 1) throw new Error(`Expected one ${category}, received ${items.length}`);
2657
+ if (items == null || items.length === 0) throw new Error(`${category} not found`, { cause: items });
2658
+ if (items.length > 1) throw new Error(`Expected one ${category}, received ${items.length}`, { cause: items });
2643
2659
  return items[0];
2644
2660
  }
2645
2661
  function parseTopLevelItem(rawOchre, category, options) {
@@ -2826,10 +2842,12 @@ const XMLContextItem = v.objectWithRest({
2826
2842
  tree: v.array(XMLContextValue),
2827
2843
  displayPath: v.string("XMLContextItem: displayPath is string and required")
2828
2844
  }, v.array(XMLContextValue), "XMLContextItem: Shape error");
2829
- const XMLContext = v.array(v.object({
2830
- context: v.array(XMLContextItem, "XMLContext: context is array of XMLContextItem"),
2831
- displayPath: v.string("XMLContext: displayPath is string and required")
2832
- }, "XMLContext: Shape error"));
2845
+ const XMLEmptyContext = v.object({ payload: v.string("XMLEmptyContext: payload is string and required") }, "XMLEmptyContext: Shape error");
2846
+ const XMLContextGroup = v.object({
2847
+ context: v.array(v.union([XMLContextItem, XMLEmptyContext]), "XMLContextGroup: context is array of XMLContextItem or XMLEmptyContext"),
2848
+ displayPath: v.string("XMLContextGroup: displayPath is string and required")
2849
+ }, "XMLContextGroup: Shape error");
2850
+ const XMLContext = v.array(v.union([XMLContextGroup, XMLEmptyContext], "XMLContext: item is XMLContextGroup or XMLEmptyContext"));
2833
2851
  const XMLEvent = v.object({
2834
2852
  dateTime: v.optional(customDateTime("XMLEvent: dateTime is not a valid datetime")),
2835
2853
  endDateTime: v.optional(customDateTime("XMLEvent: endDateTime is not a valid datetime")),
@@ -3612,13 +3630,13 @@ async function fetchGallery(params, options) {
3612
3630
  }),
3613
3631
  headers: { "Content-Type": "application/xquery" }
3614
3632
  });
3615
- if (!response.ok) throw new Error("Error fetching gallery items, please try again later.");
3633
+ if (!response.ok) throw new Error("Error fetching gallery items, please try again later.", { cause: response.statusText });
3616
3634
  const dataRaw = await response.text();
3617
3635
  const data = new XMLParser(XML_PARSER_OPTIONS).parse(dataRaw);
3618
3636
  const { success, issues, output } = v.safeParse(XMLGalleryData, data);
3619
3637
  if (!success) {
3620
3638
  logIssues(issues);
3621
- throw new Error("Failed to parse gallery XML");
3639
+ throw new Error("Failed to parse gallery XML", { cause: issues });
3622
3640
  }
3623
3641
  restoreXMLMetadata(output, data);
3624
3642
  return {
@@ -3722,13 +3740,13 @@ async function fetchItemLinks(uuid, options) {
3722
3740
  body: buildXQuery$2(parsedUuid),
3723
3741
  headers: { "Content-Type": "application/xquery" }
3724
3742
  });
3725
- if (!response.ok) throw new Error("Failed to fetch OCHRE item links");
3743
+ if (!response.ok) throw new Error("Failed to fetch OCHRE item links", { cause: response.statusText });
3726
3744
  const dataRaw = await response.text();
3727
3745
  const data = new XMLParser(XML_PARSER_OPTIONS).parse(dataRaw);
3728
3746
  const { success, issues, output } = v.safeParse(XMLItemLinksData, data);
3729
3747
  if (!success) {
3730
3748
  logIssues(issues);
3731
- throw new Error("Failed to parse OCHRE item links");
3749
+ throw new Error("Failed to parse OCHRE item links", { cause: issues });
3732
3750
  }
3733
3751
  restoreXMLMetadata(output, data);
3734
3752
  const languages = resolveItemLinksLanguages(output, requestedLanguages);
@@ -3788,7 +3806,7 @@ function inferFetchItemCategory(rawOchre) {
3788
3806
  if ("resource" in rawOchre) return "resource";
3789
3807
  if ("text" in rawOchre) return "text";
3790
3808
  if ("set" in rawOchre) return "set";
3791
- throw new Error("Could not infer OCHRE item category");
3809
+ throw new Error("Could not infer OCHRE item category", { cause: rawOchre });
3792
3810
  }
3793
3811
  /**
3794
3812
  * Validate language codes while preserving literal tuple inference.
@@ -3823,13 +3841,13 @@ async function fetchItem(uuid, options) {
3823
3841
  assertItemCategoryAllowed(options?.category, options?.containedItemCategory);
3824
3842
  const languages = options?.languages == null ? [] : parseLanguages$1(options.languages);
3825
3843
  const response = await (options?.fetch ?? fetch)(`https://ochre.lib.uchicago.edu/ochre/v2/ochre.php?uuid=${parsedUuid}&xsl=none&lang="*"`);
3826
- if (!response.ok) throw new Error("Failed to fetch OCHRE data");
3844
+ if (!response.ok) throw new Error("Failed to fetch OCHRE data", { cause: response.statusText });
3827
3845
  const dataRaw = await response.text();
3828
3846
  const data = new XMLParser(XML_PARSER_OPTIONS).parse(dataRaw);
3829
3847
  const { success, issues, output } = v.safeParse(XMLData, data);
3830
3848
  if (!success) {
3831
3849
  logIssues(issues);
3832
- throw new Error("Failed to parse OCHRE data");
3850
+ throw new Error("Failed to parse OCHRE data", { cause: issues });
3833
3851
  }
3834
3852
  restoreXMLMetadata(output, data);
3835
3853
  const category = options?.category ?? inferFetchItemCategory(output.result.ochre);
@@ -4514,7 +4532,7 @@ function buildLeafQueryExpression(context, query) {
4514
4532
  if (query.target === "property" && query.dataType !== "date" && query.dataType !== "dateTime" && !("value" in query) && query.propertyVariable != null) return buildPropertyPresenceQueryExpression({ propertyVariable: query.propertyVariable });
4515
4533
  if (query.target === "property" && (query.dataType === "date" || query.dataType === "dateTime") && query.value == null) return buildPropertyDateRangeQueryExpression(query);
4516
4534
  const searchValue = getLeafSearchValue(query);
4517
- if (searchValue == null) throw new Error("Missing searchable value for query leaf");
4535
+ if (searchValue == null) throw new Error("Missing searchable value for query leaf", { cause: query });
4518
4536
  const exactHelper = registerLeafHelper({
4519
4537
  context,
4520
4538
  query,
@@ -4589,9 +4607,9 @@ function getCompatibleIncludesGroupLeaves(query) {
4589
4607
  }
4590
4608
  function buildIncludesGroupQueryExpression(context, queries) {
4591
4609
  const firstQuery = queries[0];
4592
- if (firstQuery == null) throw new Error("Cannot build an includes group without queries");
4610
+ if (firstQuery == null) throw new Error("Cannot build an includes group without queries", { cause: queries });
4593
4611
  const groupValue = getGroupableIncludesValue(firstQuery);
4594
- if (groupValue == null) throw new Error("Cannot build an includes group without a search value");
4612
+ if (groupValue == null) throw new Error("Cannot build an includes group without a search value", { cause: firstQuery });
4595
4613
  const terms = tokenizeIncludesSearchValue({
4596
4614
  value: groupValue,
4597
4615
  isCaseSensitive: firstQuery.isCaseSensitive
@@ -4897,18 +4915,18 @@ async function fetchSetItems(params, containedItemCategories, options) {
4897
4915
  body: xquery,
4898
4916
  headers: { "Content-Type": "application/xquery" }
4899
4917
  });
4900
- if (!response.ok) throw new Error(`OCHRE API responded with status: ${response.status}`);
4918
+ if (!response.ok) throw new Error(`OCHRE API responded with status: ${response.status}`, { cause: response.statusText });
4901
4919
  const dataRaw = await response.text();
4902
4920
  const data = new XMLParser(XML_PARSER_OPTIONS).parse(dataRaw);
4903
4921
  const { success, issues, output } = v.safeParse(XMLSetItemsData, data);
4904
4922
  if (!success) {
4905
4923
  logIssues(issues);
4906
- throw new Error("Failed to parse OCHRE Set items");
4924
+ throw new Error("Failed to parse OCHRE Set items", { cause: issues });
4907
4925
  }
4908
4926
  restoreXMLMetadata(output, data);
4909
4927
  if (containedItemCategories != null) {
4910
4928
  const missingCategories = containedItemCategories.filter((category) => !hasSetItemsCategory(output.result.ochre.items, category));
4911
- if (missingCategories.length > 0) throw new Error(`No Set items found for item categories: ${missingCategories.join(", ")}`);
4929
+ if (missingCategories.length > 0) throw new Error(`No Set items found for item categories: ${missingCategories.join(", ")}`, { cause: missingCategories });
4912
4930
  }
4913
4931
  const languages = resolveSetItemsLanguages(output, requestedLanguages);
4914
4932
  const items = parseSetItems(output.result.ochre.items, {
@@ -5374,13 +5392,13 @@ async function fetchSetPropertyValues(params, options) {
5374
5392
  body: xquery,
5375
5393
  headers: { "Content-Type": "application/xquery" }
5376
5394
  });
5377
- if (!response.ok) throw new Error(`OCHRE API responded with status: ${response.status}`);
5395
+ if (!response.ok) throw new Error(`OCHRE API responded with status: ${response.status}`, { cause: response.statusText });
5378
5396
  const dataRaw = await response.text();
5379
5397
  const data = new XMLParser(XML_PARSER_OPTIONS).parse(dataRaw);
5380
5398
  const { success, issues, output } = v.safeParse(responseSchema, data);
5381
5399
  if (!success) {
5382
5400
  logIssues(issues);
5383
- throw new Error("Failed to parse OCHRE Set property values");
5401
+ throw new Error("Failed to parse OCHRE Set property values", { cause: issues });
5384
5402
  }
5385
5403
  const parsedPropertyValues = [];
5386
5404
  const parsedAttributeValues = [];
@@ -5794,7 +5812,7 @@ var WebsitePresentationReader = class WebsitePresentationReader {
5794
5812
  }
5795
5813
  requiredProperty(label, message) {
5796
5814
  const property = this.property(label);
5797
- if (property === null) throw new Error(message);
5815
+ if (property === null) throw new Error(message, { cause: this.sourceProperties });
5798
5816
  return property;
5799
5817
  }
5800
5818
  propertyByValue(label, value) {
@@ -5941,10 +5959,23 @@ function parseResponsiveCssStyles(properties) {
5941
5959
  * @returns Parsed bounds object
5942
5960
  */
5943
5961
  function parseBounds(bounds) {
5944
- const [southWest, northEast] = bounds.split(";").map((pair) => pair.split(",").map((coordinate) => Number.parseFloat(coordinate.trim())));
5945
- if (southWest?.length !== 2 || northEast?.length !== 2 || southWest.some((coordinate) => Number.isNaN(coordinate)) || northEast.some((coordinate) => Number.isNaN(coordinate))) throw new Error(`Invalid bounds: ${bounds}`);
5962
+ const [southWest, northEast] = bounds.trim().startsWith("[") ? parseJsonBounds(bounds) : bounds.split(";").map((pair) => pair.split(",").map((coordinate) => Number.parseFloat(coordinate.trim())));
5963
+ if (southWest?.length !== 2 || northEast?.length !== 2 || southWest.some((coordinate) => Number.isNaN(coordinate)) || northEast.some((coordinate) => Number.isNaN(coordinate))) throw new Error(`Invalid bounds: ${bounds}`, { cause: bounds });
5946
5964
  return [[southWest[0], southWest[1]], [northEast[0], northEast[1]]];
5947
5965
  }
5966
+ function parseJsonBounds(bounds) {
5967
+ let parsed;
5968
+ try {
5969
+ parsed = JSON.parse(bounds);
5970
+ } catch {
5971
+ throw new Error(`Invalid bounds: ${bounds}`, { cause: bounds });
5972
+ }
5973
+ if (!isNumberPairArray(parsed)) throw new Error(`Invalid bounds: ${bounds}`, { cause: bounds });
5974
+ return parsed;
5975
+ }
5976
+ function isNumberPairArray(value) {
5977
+ return Array.isArray(value) && value.every((pair) => Array.isArray(pair) && pair.every((coordinate) => typeof coordinate === "number"));
5978
+ }
5948
5979
  /**
5949
5980
  * Parses all context option arrays from an options object.
5950
5981
  *
@@ -6017,7 +6048,7 @@ function parseStylesheets(styles) {
6017
6048
  mobile: []
6018
6049
  };
6019
6050
  if (style.category === "propertyValue" || style.valueUuid != null) {
6020
- if (style.valueUuid == null) throw new Error(`Stylesheet property value "${style.variableUuid}" is missing a value UUID`);
6051
+ if (style.valueUuid == null) throw new Error(`Stylesheet property value "${style.variableUuid}" is missing a value UUID`, { cause: style });
6021
6052
  parsedStyles.push({
6022
6053
  uuid: style.valueUuid,
6023
6054
  category: "propertyValue",
@@ -6053,7 +6084,7 @@ function parseWebElementProperties(componentProperty, elementResource, options)
6053
6084
  switch (componentName) {
6054
6085
  case "3d-viewer": {
6055
6086
  const resourceLink = findWebsiteLink(websiteLinks, "resource", (link) => link.fileFormat === "model/obj");
6056
- if (resourceLink == null) throw new Error(formatComponentError("Resource link not found", componentName, elementResource));
6087
+ if (resourceLink == null) throw new Error(formatComponentError("Resource link not found", componentName, elementResource), { cause: componentProperty });
6057
6088
  const isInteractive = componentReader.valueOr("is-interactive", true);
6058
6089
  const isControlsDisplayed = componentReader.valueOr("controls-displayed", true);
6059
6090
  properties = {
@@ -6068,7 +6099,7 @@ function parseWebElementProperties(componentProperty, elementResource, options)
6068
6099
  case "advanced-search": {
6069
6100
  const boundElementPropertyUuid = componentReader.uuid("bound-element");
6070
6101
  const href = componentReader.linkTarget("link-to", transformPermanentIdentificationUrlToItemLink);
6071
- if (boundElementPropertyUuid == null && href == null) throw new Error(formatComponentError("Bound element or href not found", componentName, elementResource));
6102
+ if (boundElementPropertyUuid == null && href == null) throw new Error(formatComponentError("Bound element or href not found", componentName, elementResource), { cause: componentProperty });
6072
6103
  properties = {
6073
6104
  component: "advanced-search",
6074
6105
  boundElementUuid: boundElementPropertyUuid,
@@ -6078,7 +6109,7 @@ function parseWebElementProperties(componentProperty, elementResource, options)
6078
6109
  }
6079
6110
  case "annotated-document": {
6080
6111
  const documentLink = findWebsiteLink(websiteLinks, "resource", (link) => link.type === "internalDocument");
6081
- if (documentLink == null) throw new Error(formatComponentError("Document link not found", componentName, elementResource));
6112
+ if (documentLink == null) throw new Error(formatComponentError("Document link not found", componentName, elementResource), { cause: componentProperty });
6082
6113
  properties = {
6083
6114
  component: "annotated-document",
6084
6115
  linkUuid: documentLink.uuid
@@ -6087,7 +6118,7 @@ function parseWebElementProperties(componentProperty, elementResource, options)
6087
6118
  }
6088
6119
  case "annotated-image": {
6089
6120
  const imageLinks = getWebsiteLinks(websiteLinks, "resource").filter((link) => link.type === "image" || link.type === "IIIF");
6090
- if (imageLinks.length === 0) throw new Error(formatComponentError("Image link not found", componentName, elementResource));
6121
+ if (imageLinks.length === 0) throw new Error(formatComponentError("Image link not found", componentName, elementResource), { cause: componentProperty });
6091
6122
  const isFilterInputDisplayed = componentReader.valueOr("filter-input-displayed", true);
6092
6123
  const isOptionsDisplayed = componentReader.valueOr("options-displayed", true);
6093
6124
  const isAnnotationHighlightsDisplayed = componentReader.valueOr("annotation-highlights-displayed", true);
@@ -6104,7 +6135,7 @@ function parseWebElementProperties(componentProperty, elementResource, options)
6104
6135
  }
6105
6136
  case "audio-player": {
6106
6137
  const audioLink = findWebsiteLink(websiteLinks, "resource", (link) => link.type === "audio");
6107
- if (audioLink == null) throw new Error(formatComponentError("Audio link not found", componentName, elementResource));
6138
+ if (audioLink == null) throw new Error(formatComponentError("Audio link not found", componentName, elementResource), { cause: componentProperty });
6108
6139
  const isSpeedControlsDisplayed = componentReader.valueOr("speed-controls-displayed", true);
6109
6140
  const isVolumeControlsDisplayed = componentReader.valueOr("volume-controls-displayed", true);
6110
6141
  const isSeekBarDisplayed = componentReader.valueOr("seek-bar-displayed", true);
@@ -6120,7 +6151,7 @@ function parseWebElementProperties(componentProperty, elementResource, options)
6120
6151
  case "bibliography": {
6121
6152
  const itemLinks = websiteLinks.filter((link) => link.category !== "bibliography");
6122
6153
  const bibliographies = parseBibliographyList(elementResource.bibliographies, options);
6123
- if (itemLinks.length === 0 && bibliographies.length === 0) throw new Error(formatComponentError("No links found", componentName, elementResource));
6154
+ if (itemLinks.length === 0 && bibliographies.length === 0) throw new Error(formatComponentError("No links found", componentName, elementResource), { cause: componentProperty });
6124
6155
  const layout = componentReader.valueOr("layout", "long");
6125
6156
  const isSourceDocumentDisplayed = componentReader.valueOr("source-document-displayed", true);
6126
6157
  properties = {
@@ -6138,7 +6169,7 @@ function parseWebElementProperties(componentProperty, elementResource, options)
6138
6169
  let href = componentReader.linkTarget("navigate-to", transformPermanentIdentificationUrlToItemLink);
6139
6170
  if (href === null) {
6140
6171
  href = componentReader.linkTarget("link-to", transformPermanentIdentificationUrlToItemLink);
6141
- if (href === null) throw new Error(formatComponentError("Properties “navigate-to” or “link-to” not found", componentName, elementResource));
6172
+ if (href === null) throw new Error(formatComponentError("Properties “navigate-to” or “link-to” not found", componentName, elementResource), { cause: componentProperty });
6142
6173
  else isExternal = true;
6143
6174
  }
6144
6175
  const startIcon = componentReader.value("start-icon");
@@ -6167,7 +6198,7 @@ function parseWebElementProperties(componentProperty, elementResource, options)
6167
6198
  }
6168
6199
  case "collection": {
6169
6200
  const setLinks = getWebsiteLinks(websiteLinks, "set");
6170
- if (setLinks.length === 0) throw new Error(formatComponentError("Set links not found", componentName, elementResource));
6201
+ if (setLinks.length === 0) throw new Error(formatComponentError("Set links not found", componentName, elementResource), { cause: componentProperty });
6171
6202
  const displayedProperties = componentReader.property("use-property");
6172
6203
  const variant = componentReader.valueOr("variant", "slide");
6173
6204
  const paginationVariant = componentReader.valueOr("pagination-variant", "default");
@@ -6216,7 +6247,7 @@ function parseWebElementProperties(componentProperty, elementResource, options)
6216
6247
  break;
6217
6248
  case "entries": {
6218
6249
  const entriesLink = findWebsiteLinkByCategories(websiteLinks, ["set", "tree"]);
6219
- if (entriesLink == null) throw new Error(formatComponentError("Entries link not found", componentName, elementResource));
6250
+ if (entriesLink == null) throw new Error(formatComponentError("Entries link not found", componentName, elementResource), { cause: componentProperty });
6220
6251
  const variant = componentReader.valueOr("variant", "entry");
6221
6252
  const isFilterInputDisplayed = componentReader.valueOr("filter-input-displayed", false);
6222
6253
  properties = {
@@ -6229,7 +6260,7 @@ function parseWebElementProperties(componentProperty, elementResource, options)
6229
6260
  }
6230
6261
  case "iframe": {
6231
6262
  const webpageLink = findWebsiteLink(websiteLinks, "resource", (link) => link.type === "webpage");
6232
- if (webpageLink?.href == null) throw new Error(formatComponentError("URL not found", componentName, elementResource));
6263
+ if (webpageLink?.href == null) throw new Error(formatComponentError("URL not found", componentName, elementResource), { cause: componentProperty });
6233
6264
  properties = {
6234
6265
  component: "iframe",
6235
6266
  href: transformPermanentIdentificationUrlToItemLink(webpageLink.href),
@@ -6240,7 +6271,7 @@ function parseWebElementProperties(componentProperty, elementResource, options)
6240
6271
  }
6241
6272
  case "iiif-viewer": {
6242
6273
  const manifestLink = findWebsiteLink(websiteLinks, "resource", (link) => link.type === "IIIF");
6243
- if (manifestLink == null) throw new Error(formatComponentError("Manifest link not found", componentName, elementResource));
6274
+ if (manifestLink == null) throw new Error(formatComponentError("Manifest link not found", componentName, elementResource), { cause: componentProperty });
6244
6275
  const variant = componentReader.valueOr("variant", "universal-viewer");
6245
6276
  properties = {
6246
6277
  component: "iiif-viewer",
@@ -6250,7 +6281,7 @@ function parseWebElementProperties(componentProperty, elementResource, options)
6250
6281
  break;
6251
6282
  }
6252
6283
  case "image": {
6253
- if (websiteLinks.length === 0) throw new Error(formatComponentError("No links found", componentName, elementResource));
6284
+ if (websiteLinks.length === 0) throw new Error(formatComponentError("No links found", componentName, elementResource), { cause: componentProperty });
6254
6285
  const imageQuality = componentReader.valueOr("image-quality", "high");
6255
6286
  const images = [];
6256
6287
  for (const link of websiteLinks) images.push({
@@ -6320,7 +6351,7 @@ function parseWebElementProperties(componentProperty, elementResource, options)
6320
6351
  }
6321
6352
  case "image-gallery": {
6322
6353
  const galleryLink = findWebsiteLinkByCategories(websiteLinks, ["set", "tree"]);
6323
- if (galleryLink == null) throw new Error(formatComponentError("Image gallery link not found", componentName, elementResource));
6354
+ if (galleryLink == null) throw new Error(formatComponentError("Image gallery link not found", componentName, elementResource), { cause: componentProperty });
6324
6355
  const isFilterInputDisplayed = componentReader.valueOr("filter-input-displayed", true);
6325
6356
  properties = {
6326
6357
  component: "image-gallery",
@@ -6331,7 +6362,7 @@ function parseWebElementProperties(componentProperty, elementResource, options)
6331
6362
  }
6332
6363
  case "map": {
6333
6364
  const mapLink = findWebsiteLinkByCategories(websiteLinks, ["set", "tree"]);
6334
- if (mapLink == null) throw new Error(formatComponentError("Map link not found", componentName, elementResource));
6365
+ if (mapLink == null) throw new Error(formatComponentError("Map link not found", componentName, elementResource), { cause: componentProperty });
6335
6366
  const isInteractive = componentReader.valueOr("is-interactive", true);
6336
6367
  const isClustered = componentReader.valueOr("is-clustered", false);
6337
6368
  const isUsingPins = componentReader.valueOr("is-using-pins", false);
@@ -6360,9 +6391,9 @@ function parseWebElementProperties(componentProperty, elementResource, options)
6360
6391
  }
6361
6392
  case "query": {
6362
6393
  const setLinks = getWebsiteLinks(websiteLinks, "set");
6363
- if (setLinks.length === 0) throw new Error(formatComponentError("Set links not found", componentName, elementResource));
6394
+ if (setLinks.length === 0) throw new Error(formatComponentError("Set links not found", componentName, elementResource), { cause: componentProperty });
6364
6395
  const items = [];
6365
- if (componentProperty.properties.length === 0) throw new Error(formatComponentError("Query properties not found", componentName, elementResource));
6396
+ if (componentProperty.properties.length === 0) throw new Error(formatComponentError("Query properties not found", componentName, elementResource), { cause: componentProperty });
6366
6397
  for (const queryItem of componentProperty.properties) {
6367
6398
  const queryReader = websitePresentationReader(queryItem.properties);
6368
6399
  const label = queryReader.multilingualValue("query-prompt", options);
@@ -6372,9 +6403,9 @@ function parseWebElementProperties(componentProperty, elementResource, options)
6372
6403
  if (queryLanguage == null) throw new Error(formatComponentError("Query language not found", componentName, elementResource));
6373
6404
  const queries = [];
6374
6405
  for (const propertyVariable of propertyVariables) {
6375
- if (propertyVariable.uuid === null) throw new Error(formatComponentError("Property variable UUID not found", componentName, elementResource));
6406
+ if (propertyVariable.uuid === null) throw new Error(formatComponentError("Property variable UUID not found", componentName, elementResource), { cause: propertyVariable });
6376
6407
  const dataType = propertyVariable.dataType;
6377
- if (dataType === "coordinate") throw new Error(formatComponentError("Query prompts with data type \"coordinate\" are not supported", componentName, elementResource));
6408
+ if (dataType === "coordinate") throw new Error(formatComponentError("Query prompts with data type \"coordinate\" are not supported", componentName, elementResource), { cause: propertyVariable });
6378
6409
  queries.push({
6379
6410
  target: "property",
6380
6411
  propertyVariable: propertyVariable.uuid,
@@ -6393,7 +6424,7 @@ function parseWebElementProperties(componentProperty, elementResource, options)
6393
6424
  endIcon
6394
6425
  });
6395
6426
  }
6396
- if (items.length === 0) throw new Error(formatComponentError("No queries found", componentName, elementResource));
6427
+ if (items.length === 0) throw new Error(formatComponentError("No queries found", componentName, elementResource), { cause: componentProperty });
6397
6428
  const componentOptions = parseWebsiteOptions(elementResource.options, options);
6398
6429
  const displayedProperties = componentReader.property("use-property");
6399
6430
  const variant = componentReader.valueOr("variant", "slide");
@@ -6420,7 +6451,7 @@ function parseWebElementProperties(componentProperty, elementResource, options)
6420
6451
  }
6421
6452
  case "table": {
6422
6453
  const tableLink = findWebsiteLink(websiteLinks, "set");
6423
- if (tableLink == null) throw new Error(formatComponentError("Table link not found", componentName, elementResource));
6454
+ if (tableLink == null) throw new Error(formatComponentError("Table link not found", componentName, elementResource), { cause: componentProperty });
6424
6455
  properties = {
6425
6456
  component: "table",
6426
6457
  linkUuid: tableLink.uuid
@@ -6431,7 +6462,7 @@ function parseWebElementProperties(componentProperty, elementResource, options)
6431
6462
  const queryVariant = componentReader.valueOr("query-variant", "submit");
6432
6463
  const boundElementUuid = componentReader.uuid("bound-element");
6433
6464
  const href = componentReader.linkTarget("link-to", transformPermanentIdentificationUrlToItemLink);
6434
- if (!boundElementUuid && !href) throw new Error(formatComponentError("Bound element or href not found", componentName, elementResource));
6465
+ if (!boundElementUuid && !href) throw new Error(formatComponentError("Bound element or href not found", componentName, elementResource), { cause: componentProperty });
6435
6466
  properties = {
6436
6467
  component: "search-bar",
6437
6468
  queryVariant,
@@ -6444,7 +6475,7 @@ function parseWebElementProperties(componentProperty, elementResource, options)
6444
6475
  }
6445
6476
  case "text": {
6446
6477
  const content = elementResource.document && "content" in elementResource.document ? parseXMLContent(elementResource.document, options) : null;
6447
- if (content == null) throw new Error(formatComponentError("Content not found", componentName, elementResource));
6478
+ if (content == null) throw new Error(formatComponentError("Content not found", componentName, elementResource), { cause: componentProperty });
6448
6479
  let variantName = "block";
6449
6480
  let variant;
6450
6481
  const variantProperty = componentReader.property("variant");
@@ -6490,7 +6521,7 @@ function parseWebElementProperties(componentProperty, elementResource, options)
6490
6521
  }
6491
6522
  case "timeline": {
6492
6523
  const timelineLink = findWebsiteLink(websiteLinks, "tree");
6493
- if (timelineLink == null) throw new Error(formatComponentError("Timeline link not found", componentName, elementResource));
6524
+ if (timelineLink == null) throw new Error(formatComponentError("Timeline link not found", componentName, elementResource), { cause: componentProperty });
6494
6525
  properties = {
6495
6526
  component: "timeline",
6496
6527
  linkUuid: timelineLink.uuid
@@ -6499,7 +6530,7 @@ function parseWebElementProperties(componentProperty, elementResource, options)
6499
6530
  }
6500
6531
  case "video": {
6501
6532
  const videoLink = findWebsiteLink(websiteLinks, "resource", (link) => link.type === "video");
6502
- if (videoLink == null) throw new Error(formatComponentError("Video link not found", componentName, elementResource));
6533
+ if (videoLink == null) throw new Error(formatComponentError("Video link not found", componentName, elementResource), { cause: componentProperty });
6503
6534
  const isChaptersDisplayed = componentReader.valueOr("chapters-displayed", true);
6504
6535
  properties = {
6505
6536
  component: "video",
@@ -6512,7 +6543,7 @@ function parseWebElementProperties(componentProperty, elementResource, options)
6512
6543
  console.warn(`Invalid or non-implemented component name “${unparsedComponentName.toString()}” for the following element: “${parseStringContent(elementResource.identification.label, options)}”`);
6513
6544
  break;
6514
6545
  }
6515
- if (properties === null) throw new Error(formatComponentError("Properties not found", componentName, elementResource));
6546
+ if (properties === null) throw new Error(formatComponentError("Properties not found", componentName, elementResource), { cause: componentProperty });
6516
6547
  return properties;
6517
6548
  }
6518
6549
  function parseWebTitle(properties, identification, overrides) {
@@ -6573,7 +6604,7 @@ function parseWebpage(webpageResource, options, context, slugPrefix) {
6573
6604
  if (webpageReader.value("presentation") !== "page") return null;
6574
6605
  const identification = parseIdentification(webpageResource.identification, options);
6575
6606
  const slug = webpageResource.slug?.replace(SEGMENT_UNIQUE_SLUG_PREFIX_REGEX, "") ?? null;
6576
- if (slug == null) throw new Error(`Slug not found for page (${formatXMLWebsiteResourceMetadata(webpageResource)})`);
6607
+ if (slug == null) throw new Error(`Slug not found for page (${formatXMLWebsiteResourceMetadata(webpageResource)})`, { cause: webpageResource });
6577
6608
  const returnWebpage = {
6578
6609
  uuid: webpageResource.uuid,
6579
6610
  type: "page",
@@ -6660,7 +6691,7 @@ function parseWebsiteSegments(resources, context, options, slugPrefix) {
6660
6691
  if (!("segments" in resource)) continue;
6661
6692
  for (const tree of resource.segments.tree) {
6662
6693
  const segmentSlug = tree.identification.abbreviation == null ? null : parseStringContent(tree.identification.abbreviation, options);
6663
- if (segmentSlug == null) throw new Error(`Slug not found for segment website (website uuid “${tree.uuid}”)`);
6694
+ if (segmentSlug == null) throw new Error(`Slug not found for segment website (website uuid “${tree.uuid}”)`, { cause: tree });
6664
6695
  segments.push(parseWebsiteTree(tree, context, "segment", options, prefixSlug(segmentSlug, slugPrefix)));
6665
6696
  }
6666
6697
  }
@@ -6841,9 +6872,9 @@ function parseWebBlock(blockResource, options) {
6841
6872
  for (const resource of blockResources) {
6842
6873
  const resourceReader = websitePresentationReader(resource.properties ? parseSimplifiedProperties(resource.properties, options) : []);
6843
6874
  const resourceType = resourceReader.value("presentation");
6844
- if (resourceType !== "element") throw new Error(`Accordion only accepts elements, but got “${resourceType}” (${formatXMLWebsiteResourceMetadata(resource)})`);
6875
+ if (resourceType !== "element") throw new Error(`Accordion only accepts elements, but got “${resourceType}” (${formatXMLWebsiteResourceMetadata(resource)})`, { cause: resource });
6845
6876
  const componentType = resourceReader.nestedByValue("presentation", "element").value("component");
6846
- if (componentType !== "text") throw new Error(`Accordion only accepts text components, but got “${componentType}” (${formatXMLWebsiteResourceMetadata(resource)})`);
6877
+ if (componentType !== "text") throw new Error(`Accordion only accepts text components, but got “${componentType}” (${formatXMLWebsiteResourceMetadata(resource)})`, { cause: resource });
6847
6878
  const element = parseWebElementForAccordion(resource, options);
6848
6879
  accordionItems.push(element);
6849
6880
  }
@@ -6937,7 +6968,7 @@ function parseWebsiteProperties(properties, websiteTree, sidebar, options) {
6937
6968
  name: contactContent[0],
6938
6969
  email: contactContent[1] ?? null
6939
6970
  };
6940
- else throw new Error(`Contact property must use “name;email”, got “${contactProperty.values[0]?.content}” (website uuid “${websiteTree.uuid}”)`);
6971
+ else throw new Error(`Contact property must use “name;email”, got “${contactProperty.values[0]?.content}” (website uuid “${websiteTree.uuid}”)`, { cause: websiteTree });
6941
6972
  }
6942
6973
  returnProperties.loadingVariant = websiteReader.valueOr("loading-variant", "spinner");
6943
6974
  returnProperties.theme.isThemeToggleDisplayed = websiteReader.valueOr("supports-theme-toggle", true);
@@ -7043,8 +7074,8 @@ function parseFilterContexts(filterContextLevels, options) {
7043
7074
  return filterContextTreeLevels;
7044
7075
  }
7045
7076
  function parseWebsiteTree(websiteTree, context, type, options, slugPrefix) {
7046
- if (!websiteTree.properties) throw new Error(`Website properties not found (website uuid “${websiteTree.uuid}”)`);
7047
- if (type === "website" && websiteTree.items?.resource == null) throw new Error(`Website pages not found (website uuid “${websiteTree.uuid}”)`);
7077
+ if (!websiteTree.properties) throw new Error(`Website properties not found (website uuid “${websiteTree.uuid}”)`, { cause: websiteTree });
7078
+ if (type === "website" && websiteTree.items?.resource == null) throw new Error(`Website pages not found (website uuid “${websiteTree.uuid}”)`, { cause: websiteTree });
7048
7079
  const resources = normalizeWebsiteResources(websiteTree.items?.resource);
7049
7080
  const sidebar = parseSidebar(resources, options);
7050
7081
  const properties = parseWebsiteProperties(websiteTree.properties.property, websiteTree, sidebar, options);
@@ -7068,7 +7099,7 @@ function parseWebsite(data, options) {
7068
7099
  const parserOptions = { languages };
7069
7100
  const defaultLanguage = resolveDefaultLanguage(rawOchre, languages);
7070
7101
  const websiteTree = rawOchre.tree[0];
7071
- if (websiteTree == null) throw new Error("Website tree not found");
7102
+ if (websiteTree == null) throw new Error("Website tree not found", { cause: data });
7072
7103
  return parseWebsiteTree(websiteTree, {
7073
7104
  belongsTo: {
7074
7105
  uuid: rawOchre.uuidBelongsTo,
@@ -7089,13 +7120,13 @@ async function fetchWebsite(abbreviation, options) {
7089
7120
  try {
7090
7121
  const cleanAbbreviation = abbreviation.trim().toLocaleLowerCase("en-US");
7091
7122
  const response = await (options?.fetch ?? fetch)(`https://ochre.lib.uchicago.edu/ochre/v2/ochre.php?xquery=${encodeURIComponent(`collection('ochre/tree')/ochre[tree/identification/abbreviation/content/string='${cleanAbbreviation}']`)}&xsl=none&lang="*"`);
7092
- if (!response.ok) throw new Error("Failed to fetch website");
7123
+ if (!response.ok) throw new Error("Failed to fetch website", { cause: response.statusText });
7093
7124
  const dataRaw = await response.text();
7094
7125
  const data = new XMLParser(XML_PARSER_OPTIONS).parse(dataRaw);
7095
7126
  const { success, issues, output } = v.safeParse(XMLWebsiteData, data);
7096
7127
  if (!success) {
7097
7128
  logIssues(issues);
7098
- throw new Error("Failed to parse website XML");
7129
+ throw new Error("Failed to parse website XML", { cause: issues });
7099
7130
  }
7100
7131
  restoreXMLMetadata(output, data);
7101
7132
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ochre-sdk",
3
- "version": "1.0.4",
3
+ "version": "1.0.6",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "description": "Node.js library for working with OCHRE (Online Cultural and Historical Research Environment) data",