ochre-sdk 1.0.5 → 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 +106 -88
  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) {
@@ -5942,7 +5960,7 @@ function parseResponsiveCssStyles(properties) {
5942
5960
  */
5943
5961
  function parseBounds(bounds) {
5944
5962
  const [southWest, northEast] = bounds.trim().startsWith("[") ? parseJsonBounds(bounds) : 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}`);
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
  }
5948
5966
  function parseJsonBounds(bounds) {
@@ -5950,9 +5968,9 @@ function parseJsonBounds(bounds) {
5950
5968
  try {
5951
5969
  parsed = JSON.parse(bounds);
5952
5970
  } catch {
5953
- throw new Error(`Invalid bounds: ${bounds}`);
5971
+ throw new Error(`Invalid bounds: ${bounds}`, { cause: bounds });
5954
5972
  }
5955
- if (!isNumberPairArray(parsed)) throw new Error(`Invalid bounds: ${bounds}`);
5973
+ if (!isNumberPairArray(parsed)) throw new Error(`Invalid bounds: ${bounds}`, { cause: bounds });
5956
5974
  return parsed;
5957
5975
  }
5958
5976
  function isNumberPairArray(value) {
@@ -6030,7 +6048,7 @@ function parseStylesheets(styles) {
6030
6048
  mobile: []
6031
6049
  };
6032
6050
  if (style.category === "propertyValue" || style.valueUuid != null) {
6033
- 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 });
6034
6052
  parsedStyles.push({
6035
6053
  uuid: style.valueUuid,
6036
6054
  category: "propertyValue",
@@ -6066,7 +6084,7 @@ function parseWebElementProperties(componentProperty, elementResource, options)
6066
6084
  switch (componentName) {
6067
6085
  case "3d-viewer": {
6068
6086
  const resourceLink = findWebsiteLink(websiteLinks, "resource", (link) => link.fileFormat === "model/obj");
6069
- 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 });
6070
6088
  const isInteractive = componentReader.valueOr("is-interactive", true);
6071
6089
  const isControlsDisplayed = componentReader.valueOr("controls-displayed", true);
6072
6090
  properties = {
@@ -6081,7 +6099,7 @@ function parseWebElementProperties(componentProperty, elementResource, options)
6081
6099
  case "advanced-search": {
6082
6100
  const boundElementPropertyUuid = componentReader.uuid("bound-element");
6083
6101
  const href = componentReader.linkTarget("link-to", transformPermanentIdentificationUrlToItemLink);
6084
- 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 });
6085
6103
  properties = {
6086
6104
  component: "advanced-search",
6087
6105
  boundElementUuid: boundElementPropertyUuid,
@@ -6091,7 +6109,7 @@ function parseWebElementProperties(componentProperty, elementResource, options)
6091
6109
  }
6092
6110
  case "annotated-document": {
6093
6111
  const documentLink = findWebsiteLink(websiteLinks, "resource", (link) => link.type === "internalDocument");
6094
- 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 });
6095
6113
  properties = {
6096
6114
  component: "annotated-document",
6097
6115
  linkUuid: documentLink.uuid
@@ -6100,7 +6118,7 @@ function parseWebElementProperties(componentProperty, elementResource, options)
6100
6118
  }
6101
6119
  case "annotated-image": {
6102
6120
  const imageLinks = getWebsiteLinks(websiteLinks, "resource").filter((link) => link.type === "image" || link.type === "IIIF");
6103
- 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 });
6104
6122
  const isFilterInputDisplayed = componentReader.valueOr("filter-input-displayed", true);
6105
6123
  const isOptionsDisplayed = componentReader.valueOr("options-displayed", true);
6106
6124
  const isAnnotationHighlightsDisplayed = componentReader.valueOr("annotation-highlights-displayed", true);
@@ -6117,7 +6135,7 @@ function parseWebElementProperties(componentProperty, elementResource, options)
6117
6135
  }
6118
6136
  case "audio-player": {
6119
6137
  const audioLink = findWebsiteLink(websiteLinks, "resource", (link) => link.type === "audio");
6120
- 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 });
6121
6139
  const isSpeedControlsDisplayed = componentReader.valueOr("speed-controls-displayed", true);
6122
6140
  const isVolumeControlsDisplayed = componentReader.valueOr("volume-controls-displayed", true);
6123
6141
  const isSeekBarDisplayed = componentReader.valueOr("seek-bar-displayed", true);
@@ -6133,7 +6151,7 @@ function parseWebElementProperties(componentProperty, elementResource, options)
6133
6151
  case "bibliography": {
6134
6152
  const itemLinks = websiteLinks.filter((link) => link.category !== "bibliography");
6135
6153
  const bibliographies = parseBibliographyList(elementResource.bibliographies, options);
6136
- 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 });
6137
6155
  const layout = componentReader.valueOr("layout", "long");
6138
6156
  const isSourceDocumentDisplayed = componentReader.valueOr("source-document-displayed", true);
6139
6157
  properties = {
@@ -6151,7 +6169,7 @@ function parseWebElementProperties(componentProperty, elementResource, options)
6151
6169
  let href = componentReader.linkTarget("navigate-to", transformPermanentIdentificationUrlToItemLink);
6152
6170
  if (href === null) {
6153
6171
  href = componentReader.linkTarget("link-to", transformPermanentIdentificationUrlToItemLink);
6154
- 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 });
6155
6173
  else isExternal = true;
6156
6174
  }
6157
6175
  const startIcon = componentReader.value("start-icon");
@@ -6180,7 +6198,7 @@ function parseWebElementProperties(componentProperty, elementResource, options)
6180
6198
  }
6181
6199
  case "collection": {
6182
6200
  const setLinks = getWebsiteLinks(websiteLinks, "set");
6183
- 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 });
6184
6202
  const displayedProperties = componentReader.property("use-property");
6185
6203
  const variant = componentReader.valueOr("variant", "slide");
6186
6204
  const paginationVariant = componentReader.valueOr("pagination-variant", "default");
@@ -6229,7 +6247,7 @@ function parseWebElementProperties(componentProperty, elementResource, options)
6229
6247
  break;
6230
6248
  case "entries": {
6231
6249
  const entriesLink = findWebsiteLinkByCategories(websiteLinks, ["set", "tree"]);
6232
- 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 });
6233
6251
  const variant = componentReader.valueOr("variant", "entry");
6234
6252
  const isFilterInputDisplayed = componentReader.valueOr("filter-input-displayed", false);
6235
6253
  properties = {
@@ -6242,7 +6260,7 @@ function parseWebElementProperties(componentProperty, elementResource, options)
6242
6260
  }
6243
6261
  case "iframe": {
6244
6262
  const webpageLink = findWebsiteLink(websiteLinks, "resource", (link) => link.type === "webpage");
6245
- 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 });
6246
6264
  properties = {
6247
6265
  component: "iframe",
6248
6266
  href: transformPermanentIdentificationUrlToItemLink(webpageLink.href),
@@ -6253,7 +6271,7 @@ function parseWebElementProperties(componentProperty, elementResource, options)
6253
6271
  }
6254
6272
  case "iiif-viewer": {
6255
6273
  const manifestLink = findWebsiteLink(websiteLinks, "resource", (link) => link.type === "IIIF");
6256
- 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 });
6257
6275
  const variant = componentReader.valueOr("variant", "universal-viewer");
6258
6276
  properties = {
6259
6277
  component: "iiif-viewer",
@@ -6263,7 +6281,7 @@ function parseWebElementProperties(componentProperty, elementResource, options)
6263
6281
  break;
6264
6282
  }
6265
6283
  case "image": {
6266
- 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 });
6267
6285
  const imageQuality = componentReader.valueOr("image-quality", "high");
6268
6286
  const images = [];
6269
6287
  for (const link of websiteLinks) images.push({
@@ -6333,7 +6351,7 @@ function parseWebElementProperties(componentProperty, elementResource, options)
6333
6351
  }
6334
6352
  case "image-gallery": {
6335
6353
  const galleryLink = findWebsiteLinkByCategories(websiteLinks, ["set", "tree"]);
6336
- 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 });
6337
6355
  const isFilterInputDisplayed = componentReader.valueOr("filter-input-displayed", true);
6338
6356
  properties = {
6339
6357
  component: "image-gallery",
@@ -6344,7 +6362,7 @@ function parseWebElementProperties(componentProperty, elementResource, options)
6344
6362
  }
6345
6363
  case "map": {
6346
6364
  const mapLink = findWebsiteLinkByCategories(websiteLinks, ["set", "tree"]);
6347
- 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 });
6348
6366
  const isInteractive = componentReader.valueOr("is-interactive", true);
6349
6367
  const isClustered = componentReader.valueOr("is-clustered", false);
6350
6368
  const isUsingPins = componentReader.valueOr("is-using-pins", false);
@@ -6373,9 +6391,9 @@ function parseWebElementProperties(componentProperty, elementResource, options)
6373
6391
  }
6374
6392
  case "query": {
6375
6393
  const setLinks = getWebsiteLinks(websiteLinks, "set");
6376
- 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 });
6377
6395
  const items = [];
6378
- 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 });
6379
6397
  for (const queryItem of componentProperty.properties) {
6380
6398
  const queryReader = websitePresentationReader(queryItem.properties);
6381
6399
  const label = queryReader.multilingualValue("query-prompt", options);
@@ -6385,9 +6403,9 @@ function parseWebElementProperties(componentProperty, elementResource, options)
6385
6403
  if (queryLanguage == null) throw new Error(formatComponentError("Query language not found", componentName, elementResource));
6386
6404
  const queries = [];
6387
6405
  for (const propertyVariable of propertyVariables) {
6388
- 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 });
6389
6407
  const dataType = propertyVariable.dataType;
6390
- 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 });
6391
6409
  queries.push({
6392
6410
  target: "property",
6393
6411
  propertyVariable: propertyVariable.uuid,
@@ -6406,7 +6424,7 @@ function parseWebElementProperties(componentProperty, elementResource, options)
6406
6424
  endIcon
6407
6425
  });
6408
6426
  }
6409
- 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 });
6410
6428
  const componentOptions = parseWebsiteOptions(elementResource.options, options);
6411
6429
  const displayedProperties = componentReader.property("use-property");
6412
6430
  const variant = componentReader.valueOr("variant", "slide");
@@ -6433,7 +6451,7 @@ function parseWebElementProperties(componentProperty, elementResource, options)
6433
6451
  }
6434
6452
  case "table": {
6435
6453
  const tableLink = findWebsiteLink(websiteLinks, "set");
6436
- 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 });
6437
6455
  properties = {
6438
6456
  component: "table",
6439
6457
  linkUuid: tableLink.uuid
@@ -6444,7 +6462,7 @@ function parseWebElementProperties(componentProperty, elementResource, options)
6444
6462
  const queryVariant = componentReader.valueOr("query-variant", "submit");
6445
6463
  const boundElementUuid = componentReader.uuid("bound-element");
6446
6464
  const href = componentReader.linkTarget("link-to", transformPermanentIdentificationUrlToItemLink);
6447
- 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 });
6448
6466
  properties = {
6449
6467
  component: "search-bar",
6450
6468
  queryVariant,
@@ -6457,7 +6475,7 @@ function parseWebElementProperties(componentProperty, elementResource, options)
6457
6475
  }
6458
6476
  case "text": {
6459
6477
  const content = elementResource.document && "content" in elementResource.document ? parseXMLContent(elementResource.document, options) : null;
6460
- 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 });
6461
6479
  let variantName = "block";
6462
6480
  let variant;
6463
6481
  const variantProperty = componentReader.property("variant");
@@ -6503,7 +6521,7 @@ function parseWebElementProperties(componentProperty, elementResource, options)
6503
6521
  }
6504
6522
  case "timeline": {
6505
6523
  const timelineLink = findWebsiteLink(websiteLinks, "tree");
6506
- 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 });
6507
6525
  properties = {
6508
6526
  component: "timeline",
6509
6527
  linkUuid: timelineLink.uuid
@@ -6512,7 +6530,7 @@ function parseWebElementProperties(componentProperty, elementResource, options)
6512
6530
  }
6513
6531
  case "video": {
6514
6532
  const videoLink = findWebsiteLink(websiteLinks, "resource", (link) => link.type === "video");
6515
- 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 });
6516
6534
  const isChaptersDisplayed = componentReader.valueOr("chapters-displayed", true);
6517
6535
  properties = {
6518
6536
  component: "video",
@@ -6525,7 +6543,7 @@ function parseWebElementProperties(componentProperty, elementResource, options)
6525
6543
  console.warn(`Invalid or non-implemented component name “${unparsedComponentName.toString()}” for the following element: “${parseStringContent(elementResource.identification.label, options)}”`);
6526
6544
  break;
6527
6545
  }
6528
- 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 });
6529
6547
  return properties;
6530
6548
  }
6531
6549
  function parseWebTitle(properties, identification, overrides) {
@@ -6586,7 +6604,7 @@ function parseWebpage(webpageResource, options, context, slugPrefix) {
6586
6604
  if (webpageReader.value("presentation") !== "page") return null;
6587
6605
  const identification = parseIdentification(webpageResource.identification, options);
6588
6606
  const slug = webpageResource.slug?.replace(SEGMENT_UNIQUE_SLUG_PREFIX_REGEX, "") ?? null;
6589
- 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 });
6590
6608
  const returnWebpage = {
6591
6609
  uuid: webpageResource.uuid,
6592
6610
  type: "page",
@@ -6673,7 +6691,7 @@ function parseWebsiteSegments(resources, context, options, slugPrefix) {
6673
6691
  if (!("segments" in resource)) continue;
6674
6692
  for (const tree of resource.segments.tree) {
6675
6693
  const segmentSlug = tree.identification.abbreviation == null ? null : parseStringContent(tree.identification.abbreviation, options);
6676
- 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 });
6677
6695
  segments.push(parseWebsiteTree(tree, context, "segment", options, prefixSlug(segmentSlug, slugPrefix)));
6678
6696
  }
6679
6697
  }
@@ -6854,9 +6872,9 @@ function parseWebBlock(blockResource, options) {
6854
6872
  for (const resource of blockResources) {
6855
6873
  const resourceReader = websitePresentationReader(resource.properties ? parseSimplifiedProperties(resource.properties, options) : []);
6856
6874
  const resourceType = resourceReader.value("presentation");
6857
- 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 });
6858
6876
  const componentType = resourceReader.nestedByValue("presentation", "element").value("component");
6859
- 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 });
6860
6878
  const element = parseWebElementForAccordion(resource, options);
6861
6879
  accordionItems.push(element);
6862
6880
  }
@@ -6950,7 +6968,7 @@ function parseWebsiteProperties(properties, websiteTree, sidebar, options) {
6950
6968
  name: contactContent[0],
6951
6969
  email: contactContent[1] ?? null
6952
6970
  };
6953
- 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 });
6954
6972
  }
6955
6973
  returnProperties.loadingVariant = websiteReader.valueOr("loading-variant", "spinner");
6956
6974
  returnProperties.theme.isThemeToggleDisplayed = websiteReader.valueOr("supports-theme-toggle", true);
@@ -7056,8 +7074,8 @@ function parseFilterContexts(filterContextLevels, options) {
7056
7074
  return filterContextTreeLevels;
7057
7075
  }
7058
7076
  function parseWebsiteTree(websiteTree, context, type, options, slugPrefix) {
7059
- if (!websiteTree.properties) throw new Error(`Website properties not found (website uuid “${websiteTree.uuid}”)`);
7060
- 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 });
7061
7079
  const resources = normalizeWebsiteResources(websiteTree.items?.resource);
7062
7080
  const sidebar = parseSidebar(resources, options);
7063
7081
  const properties = parseWebsiteProperties(websiteTree.properties.property, websiteTree, sidebar, options);
@@ -7081,7 +7099,7 @@ function parseWebsite(data, options) {
7081
7099
  const parserOptions = { languages };
7082
7100
  const defaultLanguage = resolveDefaultLanguage(rawOchre, languages);
7083
7101
  const websiteTree = rawOchre.tree[0];
7084
- if (websiteTree == null) throw new Error("Website tree not found");
7102
+ if (websiteTree == null) throw new Error("Website tree not found", { cause: data });
7085
7103
  return parseWebsiteTree(websiteTree, {
7086
7104
  belongsTo: {
7087
7105
  uuid: rawOchre.uuidBelongsTo,
@@ -7102,13 +7120,13 @@ async function fetchWebsite(abbreviation, options) {
7102
7120
  try {
7103
7121
  const cleanAbbreviation = abbreviation.trim().toLocaleLowerCase("en-US");
7104
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="*"`);
7105
- if (!response.ok) throw new Error("Failed to fetch website");
7123
+ if (!response.ok) throw new Error("Failed to fetch website", { cause: response.statusText });
7106
7124
  const dataRaw = await response.text();
7107
7125
  const data = new XMLParser(XML_PARSER_OPTIONS).parse(dataRaw);
7108
7126
  const { success, issues, output } = v.safeParse(XMLWebsiteData, data);
7109
7127
  if (!success) {
7110
7128
  logIssues(issues);
7111
- throw new Error("Failed to parse website XML");
7129
+ throw new Error("Failed to parse website XML", { cause: issues });
7112
7130
  }
7113
7131
  restoreXMLMetadata(output, data);
7114
7132
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ochre-sdk",
3
- "version": "1.0.5",
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",