ochre-sdk 0.22.19 → 0.22.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.mts CHANGED
@@ -1293,6 +1293,11 @@ declare function fetchGallery(params: {
1293
1293
  * Fetches and parses an OCHRE item from the OCHRE API
1294
1294
  *
1295
1295
  * @param uuid - The UUID of the OCHRE item to fetch
1296
+ * @param category - The category of the OCHRE item to fetch
1297
+ * @param itemCategories - The categories of the OCHRE items to fetch
1298
+ * @param options - The options for the fetch
1299
+ * @param options.fetch - The fetch function to use
1300
+ * @param options.version - The version of the OCHRE API to use
1296
1301
  * @returns Object containing the parsed OCHRE item, or an error message if the fetch/parse fails
1297
1302
  */
1298
1303
  declare function fetchItem<T extends DataCategory = DataCategory, U extends DataCategory | Array<DataCategory> = (T extends "tree" ? Exclude<DataCategory, "tree"> : T extends "set" ? Array<DataCategory> : never)>(uuid: string, category?: T, itemCategories?: U, options?: {
package/dist/index.mjs CHANGED
@@ -2065,6 +2065,11 @@ async function fetchByUuid(uuid, options) {
2065
2065
  * Fetches and parses an OCHRE item from the OCHRE API
2066
2066
  *
2067
2067
  * @param uuid - The UUID of the OCHRE item to fetch
2068
+ * @param category - The category of the OCHRE item to fetch
2069
+ * @param itemCategories - The categories of the OCHRE items to fetch
2070
+ * @param options - The options for the fetch
2071
+ * @param options.fetch - The fetch function to use
2072
+ * @param options.version - The version of the OCHRE API to use
2068
2073
  * @returns Object containing the parsed OCHRE item, or an error message if the fetch/parse fails
2069
2074
  */
2070
2075
  async function fetchItem(uuid, category, itemCategories, options) {
@@ -2511,6 +2516,16 @@ function buildPropertyStringQueryExpression(params) {
2511
2516
  value,
2512
2517
  matchMode,
2513
2518
  isCaseSensitive
2519
+ }),
2520
+ rawValueQueryExpression: buildValueRawValueInnerQuery({
2521
+ value,
2522
+ matchMode,
2523
+ isCaseSensitive
2524
+ }),
2525
+ bareValueQueryExpression: buildValueDirectTextInnerQuery({
2526
+ value,
2527
+ matchMode,
2528
+ isCaseSensitive
2514
2529
  })
2515
2530
  });
2516
2531
  }
@@ -3030,6 +3045,47 @@ function buildOrderedItemsClause(sort) {
3030
3045
  })}
3031
3046
  return $item`;
3032
3047
  }
3048
+ function isExactStringPropertyQuery(query) {
3049
+ return "target" in query && query.target === "property" && query.dataType === "string" && query.value != null && query.matchMode === "exact" && query.isNegated !== true;
3050
+ }
3051
+ function getCtsQueriesWithoutExactStringPropertyQueries(queries) {
3052
+ if (queries == null) return null;
3053
+ if ("target" in queries) return isExactStringPropertyQuery(queries) ? null : queries;
3054
+ if ("or" in queries) return queries;
3055
+ const filteredChildren = [];
3056
+ for (const childQuery of queries.and) {
3057
+ const filteredChildQuery = getCtsQueriesWithoutExactStringPropertyQueries(childQuery);
3058
+ if (filteredChildQuery != null) filteredChildren.push(filteredChildQuery);
3059
+ }
3060
+ if (filteredChildren.length === 0) return null;
3061
+ return filteredChildren.length === 1 ? filteredChildren[0] ?? null : { and: filteredChildren };
3062
+ }
3063
+ function buildExactStringPropertyPredicate(query) {
3064
+ const propertyPredicates = [];
3065
+ const value = stringLiteral(query.value);
3066
+ if (query.propertyVariable != null) propertyPredicates.push(`label/@uuid = ${stringLiteral(query.propertyVariable)}`);
3067
+ propertyPredicates.push(`value[
3068
+ not(@inherited = "true")
3069
+ and (
3070
+ content[@xml:lang = ${stringLiteral(query.language)}]/string = ${value}
3071
+ or @rawValue = ${value}
3072
+ or (not(content) and text() = ${value})
3073
+ )
3074
+ ]`);
3075
+ return `.//properties/property[${propertyPredicates.join(" and ")}]`;
3076
+ }
3077
+ function buildExactStringPropertyXPathFilterExpression(queries) {
3078
+ if (queries == null) return null;
3079
+ if ("target" in queries) return isExactStringPropertyQuery(queries) ? buildExactStringPropertyPredicate(queries) : null;
3080
+ if ("or" in queries) return null;
3081
+ const childExpressions = [];
3082
+ for (const childQuery of queries.and) {
3083
+ const childExpression = buildExactStringPropertyXPathFilterExpression(childQuery);
3084
+ if (childExpression != null) childExpressions.push(childExpression);
3085
+ }
3086
+ if (childExpressions.length === 0) return null;
3087
+ return childExpressions.join(" and ");
3088
+ }
3033
3089
  /**
3034
3090
  * Build an XQuery string to fetch Set items from the OCHRE API
3035
3091
  * @param params - The parameters for the fetch
@@ -3047,7 +3103,9 @@ function buildXQuery$1(params) {
3047
3103
  const startPosition = (page - 1) * pageSize + 1;
3048
3104
  const setScopeDeclaration = `declare variable $setScopeUuids := (${setScopeUuids.map((uuid) => stringLiteral(uuid)).join(", ")});`;
3049
3105
  const baseItemsExpression = "doc()/ochre/set[@uuid = $setScopeUuids]/items/*";
3050
- const compiledQueryPlan = buildQueryPlan({ queries });
3106
+ const ctsQueries = getCtsQueriesWithoutExactStringPropertyQueries(queries);
3107
+ const exactStringPropertyXPathFilterExpression = buildExactStringPropertyXPathFilterExpression(queries);
3108
+ const compiledQueryPlan = buildQueryPlan({ queries: ctsQueries });
3051
3109
  const itemsQueryExpressions = [];
3052
3110
  const belongsToCollectionQueryExpression = buildBelongsToCollectionQueryExpression(belongsToCollectionScopeUuids, BELONGS_TO_COLLECTION_UUID);
3053
3111
  if (compiledQueryPlan.queryExpression != null) itemsQueryExpressions.push(compiledQueryPlan.queryExpression);
@@ -3056,8 +3114,11 @@ function buildXQuery$1(params) {
3056
3114
  const orderedItemsClause = buildOrderedItemsClause(sort);
3057
3115
  const xqueryDeclarations = ["xquery version \"1.0-ml\";", setScopeDeclaration];
3058
3116
  if (compiledQueryPlan.prolog !== "") xqueryDeclarations.push(compiledQueryPlan.prolog);
3059
- const itemsClause = itemsQueryExpression == null ? `let $items := ${baseItemsExpression}` : `let $query := ${itemsQueryExpression}
3060
- let $items := cts:search(${baseItemsExpression}, $query)`;
3117
+ const searchedItemsClause = itemsQueryExpression == null ? `let $searchedItems := ${baseItemsExpression}` : `let $query := ${itemsQueryExpression}
3118
+ let $searchedItems := cts:search(${baseItemsExpression}, $query)`;
3119
+ const itemsClause = exactStringPropertyXPathFilterExpression == null ? `${searchedItemsClause}
3120
+ let $items := $searchedItems` : `${searchedItemsClause}
3121
+ let $items := $searchedItems[${exactStringPropertyXPathFilterExpression}]`;
3061
3122
  return `${xqueryDeclarations.join("\n\n")}
3062
3123
 
3063
3124
  <ochre>{
@@ -3190,67 +3251,27 @@ function parsePropertyValueBooleanContent(rawValue) {
3190
3251
  if (typeof rawValue === "boolean") return rawValue;
3191
3252
  return rawValue.toString().toLocaleLowerCase("en-US") === "true";
3192
3253
  }
3193
- function getPropertyValueGroupKey(value) {
3194
- const contentKey = value.content == null ? "null" : `${typeof value.content}:${value.content.toLocaleString("en-US")}`;
3195
- return `${value.dataType}|${contentKey}`;
3196
- }
3197
- function aggregatePropertyValues(values) {
3198
- const groupedPropertyValuesMap = /* @__PURE__ */ new Map();
3199
- for (const value of values) {
3200
- const key = getPropertyValueGroupKey(value);
3201
- const existing = groupedPropertyValuesMap.get(key);
3202
- if (existing == null) {
3203
- groupedPropertyValuesMap.set(key, {
3204
- dataType: value.dataType,
3205
- content: value.content,
3206
- label: value.label,
3207
- itemUuids: new Set([value.itemUuid])
3208
- });
3209
- continue;
3210
- }
3211
- existing.itemUuids.add(value.itemUuid);
3212
- if (existing.label == null && value.label != null) existing.label = value.label;
3213
- }
3214
- const groupedPropertyValues = [];
3215
- for (const group of groupedPropertyValuesMap.values()) {
3216
- if (group.content == null) continue;
3217
- groupedPropertyValues.push({
3218
- count: group.itemUuids.size,
3219
- dataType: group.dataType,
3220
- content: group.content,
3221
- label: group.label
3222
- });
3223
- }
3224
- return groupedPropertyValues.toSorted((a, b) => {
3254
+ function sortPropertyValues(values) {
3255
+ return values.toSorted((a, b) => {
3225
3256
  if (a.count !== b.count) return b.count - a.count;
3226
3257
  if (a.label !== b.label) return a.label?.localeCompare(b.label ?? "") ?? 0;
3227
3258
  return a.content?.toString().localeCompare(b.content?.toString() ?? "") ?? 0;
3228
3259
  });
3229
3260
  }
3230
- function aggregateAttributeValues(values) {
3231
- const groupedAttributeValuesMap = /* @__PURE__ */ new Map();
3232
- for (const value of values) {
3233
- if (value.content == null || value.content === "") continue;
3234
- const existing = groupedAttributeValuesMap.get(value.content);
3235
- if (existing == null) {
3236
- groupedAttributeValuesMap.set(value.content, {
3237
- content: value.content,
3238
- itemUuids: new Set([value.itemUuid])
3239
- });
3240
- continue;
3241
- }
3242
- existing.itemUuids.add(value.itemUuid);
3243
- }
3244
- const groupedAttributeValues = [];
3245
- for (const group of groupedAttributeValuesMap.values()) groupedAttributeValues.push({
3246
- count: group.itemUuids.size,
3247
- content: group.content
3248
- });
3249
- return groupedAttributeValues.toSorted((a, b) => {
3261
+ function getPropertyValueKey(value) {
3262
+ return `${value.dataType}|${typeof value.content}:${value.content.toLocaleString("en-US")}`;
3263
+ }
3264
+ function sortAttributeValues(values) {
3265
+ return values.toSorted((a, b) => {
3250
3266
  if (a.count !== b.count) return b.count - a.count;
3251
3267
  return a.content.localeCompare(b.content);
3252
3268
  });
3253
3269
  }
3270
+ const countSchema = z.union([z.number(), z.string()]).optional().transform((val) => {
3271
+ if (val == null || val === "") return 1;
3272
+ const count = Number(val);
3273
+ return Number.isFinite(count) ? count : 1;
3274
+ });
3254
3275
  function getPropertyVariableUuidsFromQueries(queries) {
3255
3276
  const propertyVariableUuids = /* @__PURE__ */ new Set();
3256
3277
  if (queries == null) return [];
@@ -3287,8 +3308,10 @@ function getItemFilterQueriesFromPropertyValueQueries(queries) {
3287
3308
  */
3288
3309
  const propertyValueQueryItemSchema = z.object({
3289
3310
  uuid: z.string(),
3311
+ scope: z.enum(["global", "variable"]).default("global"),
3290
3312
  variableUuid: z.string().optional(),
3291
- itemUuid: z.string().optional(),
3313
+ count: countSchema,
3314
+ globalCount: countSchema.nullish(),
3292
3315
  dataType: z.string(),
3293
3316
  rawValue: fakeStringSchema.optional(),
3294
3317
  content: z.union([
@@ -3298,8 +3321,10 @@ const propertyValueQueryItemSchema = z.object({
3298
3321
  ]).optional()
3299
3322
  }).transform((val) => {
3300
3323
  const returnValue = {
3324
+ scope: val.scope,
3301
3325
  variableUuid: val.variableUuid != null && val.variableUuid !== "" ? val.variableUuid : null,
3302
- itemUuid: val.itemUuid != null && val.itemUuid !== "" ? val.itemUuid : null,
3326
+ count: val.count,
3327
+ globalCount: val.globalCount ?? null,
3303
3328
  dataType: val.dataType,
3304
3329
  content: null,
3305
3330
  label: null
@@ -3331,11 +3356,11 @@ const propertyValueQueryItemSchema = z.object({
3331
3356
  });
3332
3357
  const attributeValueQueryItemSchema = z.object({
3333
3358
  attributeType: z.enum(["bibliographies", "periods"]),
3334
- itemUuid: z.string().optional(),
3359
+ count: countSchema,
3335
3360
  content: z.string().optional()
3336
3361
  }).transform((val) => ({
3337
3362
  attributeType: val.attributeType,
3338
- itemUuid: val.itemUuid != null && val.itemUuid !== "" ? val.itemUuid : null,
3363
+ count: val.count,
3339
3364
  content: val.content != null && val.content !== "" ? val.content : null
3340
3365
  }));
3341
3366
  /**
@@ -3360,9 +3385,8 @@ const responseSchema = z.object({ result: z.union([z.object({ ochre: z.object({
3360
3385
  */
3361
3386
  function buildXQuery(params) {
3362
3387
  const { setScopeUuids, belongsToCollectionScopeUuids, queries, propertyVariableUuids, attributes, isLimitedToLeafPropertyValues } = params;
3363
- const setScopeValues = setScopeUuids.map((uuid) => stringLiteral(uuid));
3364
- const setScopeDeclaration = setScopeValues.length > 0 ? `declare variable $setScopeUuids := (${setScopeValues.join(", ")});` : "";
3365
- const baseItemsExpression = setScopeValues.length > 0 ? "doc()/ochre/set[@uuid = $setScopeUuids]/items/*" : "doc()/ochre/set/items/*";
3388
+ const setScopeDeclaration = `declare variable $setScopeUuids := (${setScopeUuids.map((uuid) => stringLiteral(uuid)).join(", ")});`;
3389
+ const baseItemsExpression = "doc()/ochre/set[@uuid = $setScopeUuids]/items/*";
3366
3390
  const compiledQueryPlan = buildQueryPlan({ queries: getItemFilterQueriesFromPropertyValueQueries(queries) });
3367
3391
  const itemsQueryExpressions = [];
3368
3392
  const belongsToCollectionQueryExpression = buildBelongsToCollectionQueryExpression(belongsToCollectionScopeUuids, BELONGS_TO_COLLECTION_UUID);
@@ -3372,39 +3396,186 @@ function buildXQuery(params) {
3372
3396
  const valueFilter = isLimitedToLeafPropertyValues ? "[not(@i)]" : "";
3373
3397
  const queryBlocks = [];
3374
3398
  const returnedSequences = [];
3375
- const xqueryDeclarations = ["xquery version \"1.0-ml\";"];
3376
- if (setScopeDeclaration !== "") xqueryDeclarations.push(setScopeDeclaration);
3399
+ const xqueryDeclarations = [
3400
+ "xquery version \"1.0-ml\";",
3401
+ "declare namespace map = \"http://marklogic.com/xdmp/map\";",
3402
+ setScopeDeclaration,
3403
+ `declare function local:increment-count($counts, $key) {
3404
+ let $current := map:get($counts, $key)
3405
+ return map:put(
3406
+ $counts,
3407
+ $key,
3408
+ if (empty($current)) then 1 else xs:integer($current) + 1
3409
+ )
3410
+ };
3411
+
3412
+ declare function local:value-display($v) {
3413
+ if ($v/content)
3414
+ then string-join($v/content[@xml:lang="eng"]//text(), "")
3415
+ else string($v)
3416
+ };
3417
+
3418
+ declare function local:value-content($data-type, $raw-value, $value-uuid, $display) {
3419
+ if ($data-type = "IDREF") then $value-uuid
3420
+ else if ($data-type = ("integer", "decimal", "time")) then
3421
+ if ($raw-value castable as xs:double)
3422
+ then string(xs:double($raw-value))
3423
+ else ""
3424
+ else if ($data-type = "boolean") then
3425
+ if ($raw-value = "") then ""
3426
+ else if (lower-case($raw-value) = "true") then "true"
3427
+ else "false"
3428
+ else if ($raw-value != "") then $raw-value
3429
+ else if ($display != "" and $display != "<unassigned>") then $display
3430
+ else ""
3431
+ };
3432
+
3433
+ declare function local:value-kind($data-type) {
3434
+ if ($data-type = ("integer", "decimal", "time")) then "number"
3435
+ else if ($data-type = "boolean") then "boolean"
3436
+ else "string"
3437
+ };
3438
+
3439
+ declare function local:property-output-raw-value($data-type, $raw-value, $content) {
3440
+ if ($data-type = ("integer", "decimal", "time", "boolean")) then $content
3441
+ else $raw-value
3442
+ };
3443
+
3444
+ declare function local:put-property-detail(
3445
+ $details,
3446
+ $key,
3447
+ $scope,
3448
+ $variable-uuid,
3449
+ $value-uuid,
3450
+ $raw-value,
3451
+ $data-type,
3452
+ $display
3453
+ ) {
3454
+ let $existing := map:get($details, $key)
3455
+ return
3456
+ if (
3457
+ empty($existing)
3458
+ or (string-length(string($existing)) = 0 and string-length($display) gt 0)
3459
+ ) then
3460
+ map:put(
3461
+ $details,
3462
+ $key,
3463
+ <propertyValue scope="{$scope}" variableUuid="{$variable-uuid}" uuid="{$value-uuid}" rawValue="{$raw-value}" dataType="{$data-type}">{$display}</propertyValue>
3464
+ )
3465
+ else ()
3466
+ };
3467
+
3468
+ declare function local:add-property-facet(
3469
+ $counts,
3470
+ $details,
3471
+ $seen,
3472
+ $key,
3473
+ $scope,
3474
+ $variable-uuid,
3475
+ $value-uuid,
3476
+ $raw-value,
3477
+ $data-type,
3478
+ $display
3479
+ ) {
3480
+ if (exists(map:get($seen, $key))) then ()
3481
+ else (
3482
+ map:put($seen, $key, true()),
3483
+ local:increment-count($counts, $key),
3484
+ local:put-property-detail($details, $key, $scope, $variable-uuid, $value-uuid, $raw-value, $data-type, $display)
3485
+ )
3486
+ };
3487
+
3488
+ declare function local:add-attribute-facet($counts, $seen, $key) {
3489
+ if (exists(map:get($seen, $key))) then ()
3490
+ else (
3491
+ map:put($seen, $key, true()),
3492
+ local:increment-count($counts, $key)
3493
+ )
3494
+ };`
3495
+ ];
3377
3496
  if (compiledQueryPlan.prolog !== "") xqueryDeclarations.push(compiledQueryPlan.prolog);
3378
3497
  if (propertyVariableUuids.length > 0) {
3379
- const propertyVariableFilters = propertyVariableUuids.map((uuid) => `@uuid="${uuid}"`).join(" or ");
3380
- queryBlocks.push(`let $matching-props := $items//property[label[${propertyVariableFilters}]]
3498
+ const propertyVariableValues = propertyVariableUuids.map((uuid) => stringLiteral(uuid));
3499
+ xqueryDeclarations.push(`declare variable $facetLabelUuids := (${propertyVariableValues.join(", ")});`);
3500
+ queryBlocks.push(`let $global-property-counts := map:map()
3501
+ let $variable-property-counts := map:map()
3502
+ let $variable-property-details := map:map()
3503
+ let $variable-property-global-keys := map:map()
3504
+ let $_property-aggregation := xdmp:eager(
3505
+ for $item in $items
3506
+ let $global-seen := map:map()
3507
+ let $variable-seen := map:map()
3508
+ return
3509
+ for $p in $item/properties/property[label/@uuid = $facetLabelUuids]
3510
+ let $variable-uuid := string($p/label/@uuid)
3511
+ for $v in $p/value${valueFilter}
3512
+ let $value-uuid := string($v/@uuid)
3513
+ let $raw-value := string($v/@rawValue)
3514
+ let $data-type := string($v/@dataType)
3515
+ let $display := local:value-display($v)
3516
+ let $content := local:value-content($data-type, $raw-value, $value-uuid, $display)
3517
+ let $value-kind := local:value-kind($data-type)
3518
+ let $output-raw-value := local:property-output-raw-value($data-type, $raw-value, $content)
3519
+ let $global-key := string-join(($data-type, $value-kind, $content), "||")
3520
+ let $variable-key := string-join(($variable-uuid, $data-type, $value-kind, $content), "||")
3521
+ where $content != ""
3522
+ return (
3523
+ local:add-attribute-facet($global-property-counts, $global-seen, $global-key),
3524
+ local:add-property-facet($variable-property-counts, $variable-property-details, $variable-seen, $variable-key, "variable", $variable-uuid, $value-uuid, $output-raw-value, $data-type, $display),
3525
+ map:put($variable-property-global-keys, $variable-key, $global-key)
3526
+ )
3527
+ )
3381
3528
 
3382
3529
  let $property-values :=
3383
- for $p in $matching-props
3384
- for $v in $p/value${valueFilter}
3385
- let $item-uuid := $v/ancestor::*[parent::items]/@uuid
3386
- let $variable-uuid := $p/label/@uuid
3387
- return <propertyValue uuid="{$v/@uuid}" rawValue="{$v/@rawValue}" dataType="{$v/@dataType}" itemUuid="{$item-uuid}" variableUuid="{$variable-uuid}">{
3388
- if ($v/content) then string-join($v/content[@xml:lang="eng"]//text(), "") else $v/text()
3389
- }</propertyValue>`);
3530
+ (
3531
+ $_property-aggregation,
3532
+ for $key in map:keys($variable-property-counts)
3533
+ let $detail := map:get($variable-property-details, $key)
3534
+ let $global-key := map:get($variable-property-global-keys, $key)
3535
+ return <propertyValue scope="variable" variableUuid="{string($detail/@variableUuid)}" uuid="{string($detail/@uuid)}" rawValue="{string($detail/@rawValue)}" dataType="{string($detail/@dataType)}" count="{map:get($variable-property-counts, $key)}" globalCount="{map:get($global-property-counts, $global-key)}">{
3536
+ string($detail)
3537
+ }</propertyValue>
3538
+ )`);
3390
3539
  returnedSequences.push("$property-values");
3391
3540
  }
3392
3541
  if (attributes.bibliographies) {
3393
- queryBlocks.push(`let $bibliography-values :=
3542
+ queryBlocks.push(`let $bibliography-counts := map:map()
3543
+ let $_bibliography-aggregation := xdmp:eager(
3394
3544
  for $item in $items
3395
- for $bibliography in $item/bibliographies/bibliography
3545
+ let $seen := map:map()
3546
+ return
3547
+ for $bibliography in $item/bibliographies/bibliography
3396
3548
  let $label := string-join($bibliography/identification/label/content[@xml:lang="eng"]//text(), "")
3397
3549
  where string-length($label) gt 0
3398
- return <attributeValue attributeType="bibliographies" itemUuid="{$item/@uuid}" content="{$label}" />`);
3550
+ return local:add-attribute-facet($bibliography-counts, $seen, $label)
3551
+ )
3552
+
3553
+ let $bibliography-values :=
3554
+ (
3555
+ $_bibliography-aggregation,
3556
+ for $label in map:keys($bibliography-counts)
3557
+ return <attributeValue attributeType="bibliographies" count="{map:get($bibliography-counts, $label)}" content="{$label}" />
3558
+ )`);
3399
3559
  returnedSequences.push("$bibliography-values");
3400
3560
  }
3401
3561
  if (attributes.periods) {
3402
- queryBlocks.push(`let $period-values :=
3562
+ queryBlocks.push(`let $period-counts := map:map()
3563
+ let $_period-aggregation := xdmp:eager(
3403
3564
  for $item in $items
3404
- for $period in $item/periods/period
3565
+ let $seen := map:map()
3566
+ return
3567
+ for $period in $item/periods/period
3405
3568
  let $label := string-join($period/identification/label/content[@xml:lang="eng"]//text(), "")
3406
3569
  where string-length($label) gt 0
3407
- return <attributeValue attributeType="periods" itemUuid="{$item/@uuid}" content="{$label}" />`);
3570
+ return local:add-attribute-facet($period-counts, $seen, $label)
3571
+ )
3572
+
3573
+ let $period-values :=
3574
+ (
3575
+ $_period-aggregation,
3576
+ for $label in map:keys($period-counts)
3577
+ return <attributeValue attributeType="periods" count="{map:get($period-counts, $label)}" content="{$label}" />
3578
+ )`);
3408
3579
  returnedSequences.push("$period-values");
3409
3580
  }
3410
3581
  const itemsClause = itemsQueryExpression == null ? `let $items := ${baseItemsExpression}` : `let $query := ${itemsQueryExpression}
@@ -3470,35 +3641,50 @@ async function fetchSetPropertyValues(params, options) {
3470
3641
  if (parsedResultRaw.result.ochre.propertyValue != null) parsedPropertyValues.push(...Array.isArray(parsedResultRaw.result.ochre.propertyValue) ? parsedResultRaw.result.ochre.propertyValue : [parsedResultRaw.result.ochre.propertyValue]);
3471
3642
  if (parsedResultRaw.result.ochre.attributeValue != null) parsedAttributeValues.push(...Array.isArray(parsedResultRaw.result.ochre.attributeValue) ? parsedResultRaw.result.ochre.attributeValue : [parsedResultRaw.result.ochre.attributeValue]);
3472
3643
  }
3473
- const propertyValuesByPropertyVariableUuidRaw = {};
3474
- const flattenedPropertyValues = [];
3644
+ const propertyValuesByPropertyVariableUuid = {};
3645
+ const flattenedPropertyValuesByKey = /* @__PURE__ */ new Map();
3475
3646
  for (const propertyValue of parsedPropertyValues) {
3476
- const aggregatePropertyValueItem = {
3477
- itemUuid: propertyValue.itemUuid,
3647
+ if (propertyValue.content == null) continue;
3648
+ const propertyValueItem = {
3649
+ count: propertyValue.count,
3478
3650
  dataType: propertyValue.dataType,
3479
3651
  content: propertyValue.content,
3480
3652
  label: propertyValue.label
3481
3653
  };
3482
- flattenedPropertyValues.push(aggregatePropertyValueItem);
3483
- if (propertyValue.variableUuid == null) continue;
3484
- (propertyValuesByPropertyVariableUuidRaw[propertyValue.variableUuid] ??= []).push(aggregatePropertyValueItem);
3485
- }
3486
- const propertyValuesByPropertyVariableUuid = {};
3487
- for (const [propertyVariableUuid, values] of Object.entries(propertyValuesByPropertyVariableUuidRaw)) {
3488
- const aggregatedValues = aggregatePropertyValues(values);
3489
- if (aggregatedValues.length > 0) propertyValuesByPropertyVariableUuid[propertyVariableUuid] = aggregatedValues;
3654
+ const globalPropertyValueItem = {
3655
+ count: propertyValue.globalCount ?? propertyValue.count,
3656
+ dataType: propertyValue.dataType,
3657
+ content: propertyValue.content,
3658
+ label: propertyValue.label
3659
+ };
3660
+ const globalPropertyValueKey = getPropertyValueKey({
3661
+ dataType: globalPropertyValueItem.dataType,
3662
+ content: propertyValue.content
3663
+ });
3664
+ const existingGlobalPropertyValue = flattenedPropertyValuesByKey.get(globalPropertyValueKey);
3665
+ if (existingGlobalPropertyValue == null) flattenedPropertyValuesByKey.set(globalPropertyValueKey, globalPropertyValueItem);
3666
+ else if (existingGlobalPropertyValue.label == null && globalPropertyValueItem.label != null) existingGlobalPropertyValue.label = globalPropertyValueItem.label;
3667
+ if (propertyValue.scope === "global") continue;
3668
+ if (propertyValue.variableUuid != null) (propertyValuesByPropertyVariableUuid[propertyValue.variableUuid] ??= []).push(propertyValueItem);
3490
3669
  }
3491
- const attributeValuesByTypeRaw = {
3670
+ for (const [propertyVariableUuid, values] of Object.entries(propertyValuesByPropertyVariableUuid)) propertyValuesByPropertyVariableUuid[propertyVariableUuid] = sortPropertyValues(values);
3671
+ const attributeValuesByType = {
3492
3672
  bibliographies: [],
3493
3673
  periods: []
3494
3674
  };
3495
- for (const attributeValue of parsedAttributeValues) attributeValuesByTypeRaw[attributeValue.attributeType].push(attributeValue);
3675
+ for (const attributeValue of parsedAttributeValues) {
3676
+ if (attributeValue.content == null || attributeValue.content === "") continue;
3677
+ attributeValuesByType[attributeValue.attributeType].push({
3678
+ count: attributeValue.count,
3679
+ content: attributeValue.content
3680
+ });
3681
+ }
3496
3682
  return {
3497
- propertyValues: aggregatePropertyValues(flattenedPropertyValues),
3683
+ propertyValues: sortPropertyValues([...flattenedPropertyValuesByKey.values()]),
3498
3684
  propertyValuesByPropertyVariableUuid,
3499
3685
  attributeValues: {
3500
- bibliographies: attributes.bibliographies ? aggregateAttributeValues(attributeValuesByTypeRaw.bibliographies) : null,
3501
- periods: attributes.periods ? aggregateAttributeValues(attributeValuesByTypeRaw.periods) : null
3686
+ bibliographies: attributes.bibliographies ? sortAttributeValues(attributeValuesByType.bibliographies) : null,
3687
+ periods: attributes.periods ? sortAttributeValues(attributeValuesByType.periods) : null
3502
3688
  },
3503
3689
  error: null
3504
3690
  };
@@ -3869,6 +4055,15 @@ function filterProperties(property, filter, options = DEFAULT_OPTIONS) {
3869
4055
  //#region src/utils/parse/website.ts
3870
4056
  const SEGMENT_UNIQUE_SLUG_PREFIX_REGEX = /^\$[^-]*-/;
3871
4057
  const TRAILING_SLASH_REGEX = /\/$/;
4058
+ function formatRawResourceMetadata(resource) {
4059
+ const metadata = [`label “${parseStringContent(resource.identification.label)}”`, `uuid “${resource.uuid}”`];
4060
+ if (resource.slug != null) metadata.push(`slug “${resource.slug}”`);
4061
+ if (resource.identification.abbreviation != null) metadata.push(`abbreviation “${parseFakeStringOrContent(resource.identification.abbreviation)}”`);
4062
+ return metadata.join(", ");
4063
+ }
4064
+ function formatComponentError(message, componentName, elementResource) {
4065
+ return `${message} for component “${componentName ?? "(unknown)"}” (${formatRawResourceMetadata(elementResource)})`;
4066
+ }
3872
4067
  /**
3873
4068
  * Extracts CSS style properties for a given presentation variant.
3874
4069
  *
@@ -3992,7 +4187,7 @@ function parseWebElementProperties(componentProperty, elementResource) {
3992
4187
  switch (componentName) {
3993
4188
  case "3d-viewer": {
3994
4189
  const resourceLink = links.find((link) => link.category === "resource" && link.fileFormat === "model/obj");
3995
- if (resourceLink?.uuid == null) throw new Error(`Resource link not found for the following component: “${componentName}”`);
4190
+ if (resourceLink?.uuid == null) throw new Error(formatComponentError("Resource link not found", componentName, elementResource));
3996
4191
  let isInteractive = getPropertyValueContentByLabel(componentProperty.properties, "is-interactive");
3997
4192
  isInteractive ??= true;
3998
4193
  let isControlsDisplayed = getPropertyValueContentByLabel(componentProperty.properties, "controls-displayed");
@@ -4010,7 +4205,7 @@ function parseWebElementProperties(componentProperty, elementResource) {
4010
4205
  const boundElementPropertyUuid = getPropertyByLabel(componentProperty.properties, "bound-element")?.values[0]?.uuid ?? null;
4011
4206
  const linkToProperty = getPropertyByLabel(componentProperty.properties, "link-to");
4012
4207
  const href = linkToProperty?.values[0]?.href != null ? transformPermanentIdentificationUrl(linkToProperty.values[0].href) : linkToProperty?.values[0]?.slug ?? null;
4013
- if (boundElementPropertyUuid == null && href == null) throw new Error(`Bound element or href not found for the following component: “${componentName}”`);
4208
+ if (boundElementPropertyUuid == null && href == null) throw new Error(formatComponentError("Bound element or href not found", componentName, elementResource));
4014
4209
  properties = {
4015
4210
  component: "advanced-search",
4016
4211
  boundElementUuid: boundElementPropertyUuid,
@@ -4020,7 +4215,7 @@ function parseWebElementProperties(componentProperty, elementResource) {
4020
4215
  }
4021
4216
  case "annotated-document": {
4022
4217
  const documentLink = links.find((link) => link.type === "internalDocument");
4023
- if (documentLink?.uuid == null) throw new Error(`Document link not found for the following component: “${componentName}”`);
4218
+ if (documentLink?.uuid == null) throw new Error(formatComponentError("Document link not found", componentName, elementResource));
4024
4219
  properties = {
4025
4220
  component: "annotated-document",
4026
4221
  linkUuid: documentLink.uuid
@@ -4029,7 +4224,7 @@ function parseWebElementProperties(componentProperty, elementResource) {
4029
4224
  }
4030
4225
  case "annotated-image": {
4031
4226
  const imageLinks = links.filter((link) => link.type === "image" || link.type === "IIIF");
4032
- if (imageLinks.length === 0 || imageLinks[0].uuid == null) throw new Error(`Image link not found for the following component: “${componentName}”`);
4227
+ if (imageLinks.length === 0 || imageLinks[0].uuid == null) throw new Error(formatComponentError("Image link not found", componentName, elementResource));
4033
4228
  let isFilterInputDisplayed = getPropertyValueContentByLabel(componentProperty.properties, "filter-input-displayed");
4034
4229
  isFilterInputDisplayed ??= true;
4035
4230
  let isOptionsDisplayed = getPropertyValueContentByLabel(componentProperty.properties, "options-displayed");
@@ -4050,7 +4245,7 @@ function parseWebElementProperties(componentProperty, elementResource) {
4050
4245
  }
4051
4246
  case "audio-player": {
4052
4247
  const audioLink = links.find((link) => link.type === "audio");
4053
- if (audioLink?.uuid == null) throw new Error(`Audio link not found for the following component: “${componentName}”`);
4248
+ if (audioLink?.uuid == null) throw new Error(formatComponentError("Audio link not found", componentName, elementResource));
4054
4249
  let isSpeedControlsDisplayed = getPropertyValueContentByLabel(componentProperty.properties, "speed-controls-displayed");
4055
4250
  isSpeedControlsDisplayed ??= true;
4056
4251
  let isVolumeControlsDisplayed = getPropertyValueContentByLabel(componentProperty.properties, "volume-controls-displayed");
@@ -4069,7 +4264,7 @@ function parseWebElementProperties(componentProperty, elementResource) {
4069
4264
  case "bibliography": {
4070
4265
  const itemLinks = links.filter((link) => link.category !== "bibliography");
4071
4266
  const bibliographyLink = links.find((link) => link.category === "bibliography");
4072
- if (itemLinks.length === 0 && bibliographyLink?.bibliographies == null) throw new Error(`No links found for the following component: “${componentName}”`);
4267
+ if (itemLinks.length === 0 && bibliographyLink?.bibliographies == null) throw new Error(formatComponentError("No links found", componentName, elementResource));
4073
4268
  let layout = getPropertyValueContentByLabel(componentProperty.properties, "layout");
4074
4269
  layout ??= "long";
4075
4270
  let isSourceDocumentDisplayed = getPropertyValueContentByLabel(componentProperty.properties, "source-document-displayed");
@@ -4092,7 +4287,7 @@ function parseWebElementProperties(componentProperty, elementResource) {
4092
4287
  if (href === null) {
4093
4288
  const linkToProperty = getPropertyByLabel(componentProperty.properties, "link-to");
4094
4289
  href = linkToProperty?.values[0]?.href != null ? transformPermanentIdentificationUrl(linkToProperty.values[0].href) : linkToProperty?.values[0]?.slug ?? null;
4095
- if (href === null) throw new Error(`Properties “navigate-to” or “link-to” not found for the following component: “${componentName}”`);
4290
+ if (href === null) throw new Error(formatComponentError("Properties “navigate-to” or “link-to” not found", componentName, elementResource));
4096
4291
  else isExternal = true;
4097
4292
  }
4098
4293
  let startIcon = getPropertyValueContentByLabel(componentProperty.properties, "start-icon");
@@ -4123,7 +4318,7 @@ function parseWebElementProperties(componentProperty, elementResource) {
4123
4318
  }
4124
4319
  case "collection": {
4125
4320
  const setLinks = links.filter((link) => link.category === "set");
4126
- if (setLinks.every((link) => link.uuid === null)) throw new Error(`Set links not found for the following component: “${componentName}”`);
4321
+ if (setLinks.every((link) => link.uuid === null)) throw new Error(formatComponentError("Set links not found", componentName, elementResource));
4127
4322
  const displayedProperties = getPropertyByLabel(componentProperty.properties, "use-property");
4128
4323
  let variant = getPropertyValueContentByLabel(componentProperty.properties, "variant");
4129
4324
  variant ??= "slide";
@@ -4202,7 +4397,7 @@ function parseWebElementProperties(componentProperty, elementResource) {
4202
4397
  }
4203
4398
  case "entries": {
4204
4399
  const entriesLink = links.find((link) => link.category === "tree" || link.category === "set");
4205
- if (entriesLink?.uuid == null) throw new Error(`Entries link not found for the following component: “${componentName}”`);
4400
+ if (entriesLink?.uuid == null) throw new Error(formatComponentError("Entries link not found", componentName, elementResource));
4206
4401
  let variant = getPropertyValueContentByLabel(componentProperty.properties, "variant");
4207
4402
  variant ??= "entry";
4208
4403
  let isFilterInputDisplayed = getPropertyValueContentByLabel(componentProperty.properties, "filter-input-displayed");
@@ -4217,7 +4412,7 @@ function parseWebElementProperties(componentProperty, elementResource) {
4217
4412
  }
4218
4413
  case "iframe": {
4219
4414
  const href = links.find((link) => link.type === "webpage")?.href;
4220
- if (!href) throw new Error(`URL not found for the following component: “${componentName}”`);
4415
+ if (!href) throw new Error(formatComponentError("URL not found", componentName, elementResource));
4221
4416
  const height = getPropertyValueContentByLabel(componentProperty.properties, "height");
4222
4417
  const width = getPropertyValueContentByLabel(componentProperty.properties, "width");
4223
4418
  properties = {
@@ -4230,7 +4425,7 @@ function parseWebElementProperties(componentProperty, elementResource) {
4230
4425
  }
4231
4426
  case "iiif-viewer": {
4232
4427
  const manifestLink = links.find((link) => link.type === "IIIF");
4233
- if (manifestLink?.uuid == null) throw new Error(`Manifest link not found for the following component: “${componentName}”`);
4428
+ if (manifestLink?.uuid == null) throw new Error(formatComponentError("Manifest link not found", componentName, elementResource));
4234
4429
  let variant = getPropertyValueContentByLabel(componentProperty.properties, "variant");
4235
4430
  variant ??= "universal-viewer";
4236
4431
  properties = {
@@ -4241,7 +4436,7 @@ function parseWebElementProperties(componentProperty, elementResource) {
4241
4436
  break;
4242
4437
  }
4243
4438
  case "image": {
4244
- if (links.length === 0) throw new Error(`No links found for the following component: “${componentName}”`);
4439
+ if (links.length === 0) throw new Error(formatComponentError("No links found", componentName, elementResource));
4245
4440
  let imageQuality = getPropertyValueContentByLabel(componentProperty.properties, "image-quality");
4246
4441
  imageQuality ??= "high";
4247
4442
  const images = [];
@@ -4329,7 +4524,7 @@ function parseWebElementProperties(componentProperty, elementResource) {
4329
4524
  }
4330
4525
  case "image-gallery": {
4331
4526
  const galleryLink = links.find((link) => link.category === "tree" || link.category === "set");
4332
- if (galleryLink?.uuid == null) throw new Error(`Image gallery link not found for the following component: “${componentName}”`);
4527
+ if (galleryLink?.uuid == null) throw new Error(formatComponentError("Image gallery link not found", componentName, elementResource));
4333
4528
  let isFilterInputDisplayed = getPropertyValueContentByLabel(componentProperty.properties, "filter-input-displayed");
4334
4529
  isFilterInputDisplayed ??= true;
4335
4530
  properties = {
@@ -4341,7 +4536,7 @@ function parseWebElementProperties(componentProperty, elementResource) {
4341
4536
  }
4342
4537
  case "map": {
4343
4538
  const mapLink = links.find((link) => link.category === "set" || link.category === "tree");
4344
- if (mapLink?.uuid == null) throw new Error(`Map link not found for the following component: “${componentName}”`);
4539
+ if (mapLink?.uuid == null) throw new Error(formatComponentError("Map link not found", componentName, elementResource));
4345
4540
  let isInteractive = getPropertyValueContentByLabel(componentProperty.properties, "is-interactive");
4346
4541
  isInteractive ??= true;
4347
4542
  let isClustered = getPropertyValueContentByLabel(componentProperty.properties, "is-clustered");
@@ -4376,17 +4571,17 @@ function parseWebElementProperties(componentProperty, elementResource) {
4376
4571
  }
4377
4572
  case "query": {
4378
4573
  const setLinks = links.filter((link) => link.category === "set");
4379
- if (setLinks.every((link) => link.uuid === null)) throw new Error(`Set links not found for the following component: “${componentName}”`);
4574
+ if (setLinks.every((link) => link.uuid === null)) throw new Error(formatComponentError("Set links not found", componentName, elementResource));
4380
4575
  const items = [];
4381
- if (componentProperty.properties.length === 0) throw new Error(`Query properties not found for the following component: “${componentName}”`);
4576
+ if (componentProperty.properties.length === 0) throw new Error(formatComponentError("Query properties not found", componentName, elementResource));
4382
4577
  for (const queryItem of componentProperty.properties) {
4383
4578
  const querySubProperties = queryItem.properties;
4384
4579
  const label = getPropertyValueContentByLabel(querySubProperties, "query-prompt");
4385
4580
  if (label === null) continue;
4386
4581
  const queries = (getPropertyByLabel(querySubProperties, "use-property")?.values.filter((value) => value.uuid !== null) ?? []).map((propertyVariable) => {
4387
- if (propertyVariable.uuid === null) throw new Error(`Property variable UUID not found for the following component: “${componentName}”`);
4582
+ if (propertyVariable.uuid === null) throw new Error(formatComponentError("Property variable UUID not found", componentName, elementResource));
4388
4583
  const dataType = propertyVariable.dataType;
4389
- if (dataType === "coordinate") throw new Error(`Query prompts with data type "coordinate" are not supported for the following component: “${componentName}”`);
4584
+ if (dataType === "coordinate") throw new Error(formatComponentError("Query prompts with data type \"coordinate\" are not supported", componentName, elementResource));
4390
4585
  return {
4391
4586
  target: "property",
4392
4587
  propertyVariable: propertyVariable.uuid,
@@ -4407,7 +4602,7 @@ function parseWebElementProperties(componentProperty, elementResource) {
4407
4602
  endIcon
4408
4603
  });
4409
4604
  }
4410
- if (items.length === 0) throw new Error(`No queries found for the following component: “${componentName}”`);
4605
+ if (items.length === 0) throw new Error(formatComponentError("No queries found", componentName, elementResource));
4411
4606
  const options = {
4412
4607
  scopes: elementResource.options?.scopes != null ? ensureArray(elementResource.options.scopes.scope).map((scope) => ({
4413
4608
  uuid: scope.uuid.content,
@@ -4453,7 +4648,7 @@ function parseWebElementProperties(componentProperty, elementResource) {
4453
4648
  }
4454
4649
  case "table": {
4455
4650
  const tableLink = links.find((link) => link.category === "set");
4456
- if (tableLink?.uuid == null) throw new Error(`Table link not found for the following component: “${componentName}”`);
4651
+ if (tableLink?.uuid == null) throw new Error(formatComponentError("Table link not found", componentName, elementResource));
4457
4652
  properties = {
4458
4653
  component: "table",
4459
4654
  linkUuid: tableLink.uuid
@@ -4467,7 +4662,7 @@ function parseWebElementProperties(componentProperty, elementResource) {
4467
4662
  const boundElementUuid = getPropertyByLabel(componentProperty.properties, "bound-element")?.values[0]?.uuid ?? null;
4468
4663
  const linkToProperty = getPropertyByLabel(componentProperty.properties, "link-to");
4469
4664
  const href = linkToProperty?.values[0]?.href != null ? transformPermanentIdentificationUrl(linkToProperty.values[0].href) : linkToProperty?.values[0]?.slug ?? null;
4470
- if (!boundElementUuid && !href) throw new Error(`Bound element or href not found for the following component: “${componentName}”`);
4665
+ if (!boundElementUuid && !href) throw new Error(formatComponentError("Bound element or href not found", componentName, elementResource));
4471
4666
  let placeholder = getPropertyValueContentByLabel(componentProperty.properties, "placeholder-text");
4472
4667
  placeholder ??= null;
4473
4668
  let baseFilterQueries = getPropertyValueContentByLabel(componentProperty.properties, "base-filter-queries");
@@ -4484,7 +4679,7 @@ function parseWebElementProperties(componentProperty, elementResource) {
4484
4679
  }
4485
4680
  case "text": {
4486
4681
  const content = elementResource.document && "content" in elementResource.document ? parseDocument(elementResource.document.content) : null;
4487
- if (!content) throw new Error(`Content not found for the following component: “${componentName}”`);
4682
+ if (!content) throw new Error(formatComponentError("Content not found", componentName, elementResource));
4488
4683
  let variantName = "block";
4489
4684
  let variant;
4490
4685
  const variantProperty = getPropertyByLabel(componentProperty.properties, "variant");
@@ -4511,7 +4706,7 @@ function parseWebElementProperties(componentProperty, elementResource) {
4511
4706
  }
4512
4707
  case "timeline": {
4513
4708
  const timelineLink = links.find((link) => link.category === "tree");
4514
- if (timelineLink?.uuid == null) throw new Error(`Timeline link not found for the following component: “${componentName}”`);
4709
+ if (timelineLink?.uuid == null) throw new Error(formatComponentError("Timeline link not found", componentName, elementResource));
4515
4710
  properties = {
4516
4711
  component: "timeline",
4517
4712
  linkUuid: timelineLink.uuid
@@ -4520,7 +4715,7 @@ function parseWebElementProperties(componentProperty, elementResource) {
4520
4715
  }
4521
4716
  case "video": {
4522
4717
  const videoLink = links.find((link) => link.type === "video");
4523
- if (videoLink?.uuid == null) throw new Error(`Video link not found for the following component: “${componentName}”`);
4718
+ if (videoLink?.uuid == null) throw new Error(formatComponentError("Video link not found", componentName, elementResource));
4524
4719
  let isChaptersDisplayed = getPropertyValueContentByLabel(componentProperty.properties, "chapters-displayed");
4525
4720
  isChaptersDisplayed ??= true;
4526
4721
  properties = {
@@ -4534,7 +4729,7 @@ function parseWebElementProperties(componentProperty, elementResource) {
4534
4729
  console.warn(`Invalid or non-implemented component name “${unparsedComponentName?.toString() ?? "(unknown)"}” for the following element: “${parseStringContent(elementResource.identification.label)}”`);
4535
4730
  break;
4536
4731
  }
4537
- if (properties === null) throw new Error(`Properties not found for the following component: “${componentName}”`);
4732
+ if (properties === null) throw new Error(formatComponentError("Properties not found", componentName, elementResource));
4538
4733
  return properties;
4539
4734
  }
4540
4735
  function parseWebTitle(properties, identification, overrides) {
@@ -4570,9 +4765,9 @@ function parseWebElement(elementResource) {
4570
4765
  const identification = parseIdentification(elementResource.identification);
4571
4766
  const elementProperties = elementResource.properties?.property ? parseProperties(Array.isArray(elementResource.properties.property) ? elementResource.properties.property : [elementResource.properties.property]) : [];
4572
4767
  const presentationProperty = getPropertyByLabel(elementProperties, "presentation");
4573
- if (presentationProperty === null) throw new Error(`Presentation property not found for element “${identification.label}”`);
4768
+ if (presentationProperty === null) throw new Error(`Presentation property not found for element (${formatRawResourceMetadata(elementResource)})`);
4574
4769
  const componentProperty = getPropertyByLabel(presentationProperty.properties, "component");
4575
- if (componentProperty === null) throw new Error(`Component for element “${identification.label}” not found`);
4770
+ if (componentProperty === null) throw new Error(`Component property not found for element (${formatRawResourceMetadata(elementResource)})`);
4576
4771
  const properties = parseWebElementProperties(componentProperty, elementResource);
4577
4772
  const cssStyles = parseResponsiveCssStyles(elementProperties);
4578
4773
  const title = parseWebTitle(elementProperties, identification, {
@@ -4629,7 +4824,7 @@ function parseWebpage(webpageResource, slugPrefix) {
4629
4824
  if (webpageProperties.length === 0 || getPropertyValueContentByLabel(webpageProperties, "presentation") !== "page") return null;
4630
4825
  const identification = parseIdentification(webpageResource.identification);
4631
4826
  const slug = webpageResource.slug?.replace(SEGMENT_UNIQUE_SLUG_PREFIX_REGEX, "") ?? null;
4632
- if (slug == null) throw new Error(`Slug not found for page “${identification.label}”`);
4827
+ if (slug == null) throw new Error(`Slug not found for page (${formatRawResourceMetadata(webpageResource)})`);
4633
4828
  const returnWebpage = {
4634
4829
  uuid: webpageResource.uuid,
4635
4830
  type: "page",
@@ -4724,7 +4919,7 @@ function parseWebSegment(segmentResource, slugPrefix) {
4724
4919
  if (webpageProperties.length === 0 || getPropertyValueContentByLabel(webpageProperties, "presentation") !== "segment") return null;
4725
4920
  const identification = parseIdentification(segmentResource.identification);
4726
4921
  const slug = segmentResource.identification.abbreviation != null ? parseFakeStringOrContent(segmentResource.identification.abbreviation) : null;
4727
- if (slug == null) throw new Error(`Slug not found for segment “${identification.label}”`);
4922
+ if (slug == null) throw new Error(`Slug not found for segment (${formatRawResourceMetadata(segmentResource)})`);
4728
4923
  const returnSegment = {
4729
4924
  uuid: segmentResource.uuid,
4730
4925
  type: "segment",
@@ -4761,7 +4956,7 @@ function parseWebSegmentItem(segmentItemResource, slugPrefix) {
4761
4956
  if (webpageProperties.length === 0 || getPropertyValueContentByLabel(webpageProperties, "presentation") !== "segment-item") return null;
4762
4957
  const identification = parseIdentification(segmentItemResource.identification);
4763
4958
  const slug = segmentItemResource.identification.abbreviation != null ? parseFakeStringOrContent(segmentItemResource.identification.abbreviation) : null;
4764
- if (slug == null) throw new Error(`Slug not found for segment item “${identification.label}”`);
4959
+ if (slug == null) throw new Error(`Slug not found for segment item (${formatRawResourceMetadata(segmentItemResource)})`);
4765
4960
  const returnSegmentItem = {
4766
4961
  uuid: segmentItemResource.uuid,
4767
4962
  type: "segment-item",
@@ -4987,9 +5182,9 @@ function parseWebBlock(blockResource) {
4987
5182
  for (const resource of blockResources) {
4988
5183
  const resourceProperties = resource.properties ? parseProperties(ensureArray(resource.properties.property)) : [];
4989
5184
  const resourceType = getPropertyValueContentByLabel(resourceProperties, "presentation");
4990
- if (resourceType !== "element") throw new Error(`Accordion only accepts elements, but got “${resourceType}” for the following resource: “${parseStringContent(resource.identification.label)}”`);
5185
+ if (resourceType !== "element") throw new Error(`Accordion only accepts elements, but got “${resourceType}” (${formatRawResourceMetadata(resource)})`);
4991
5186
  const componentType = getPropertyValueContentByLabel(getPropertyByLabel(resourceProperties, "presentation")?.properties ?? [], "component");
4992
- if (componentType !== "text") throw new Error(`Accordion only accepts text components, but got “${componentType}” for the following resource: “${parseStringContent(resource.identification.label)}”`);
5187
+ if (componentType !== "text") throw new Error(`Accordion only accepts text components, but got “${componentType}” (${formatRawResourceMetadata(resource)})`);
4993
5188
  const element = parseWebElementForAccordion(resource);
4994
5189
  accordionItems.push(element);
4995
5190
  }
@@ -5088,7 +5283,7 @@ function parseWebsiteProperties(properties, websiteTree, sidebar) {
5088
5283
  name: contactContent[0],
5089
5284
  email: contactContent[1] ?? null
5090
5285
  };
5091
- else throw new Error(`Contact property must be in the format “name;email”, but got “${contactProperty.values[0]?.content}”`);
5286
+ else throw new Error(`Contact property must use “name;email”, got “${contactProperty.values[0]?.content}” (website uuid “${websiteTree.uuid}”)`);
5092
5287
  }
5093
5288
  returnProperties.loadingVariant = getPropertyValueContentByLabel(websiteProperties, "loading-variant") ?? "spinner";
5094
5289
  returnProperties.theme.isThemeToggleDisplayed = getPropertyValueContentByLabel(websiteProperties, "supports-theme-toggle") ?? true;
@@ -5208,8 +5403,8 @@ function parseFilterContexts(filterContextLevels) {
5208
5403
  return filterContextTreeLevels;
5209
5404
  }
5210
5405
  function parseWebsite(websiteTree, metadata, belongsTo, { version = 2 } = {}) {
5211
- if (!websiteTree.properties) throw new Error("Website properties not found");
5212
- if (typeof websiteTree.items === "string" || !("resource" in websiteTree.items)) throw new Error("Website pages not found");
5406
+ if (!websiteTree.properties) throw new Error(`Website properties not found (website uuid “${websiteTree.uuid}”)`);
5407
+ if (typeof websiteTree.items === "string" || !("resource" in websiteTree.items)) throw new Error(`Website pages not found (website uuid “${websiteTree.uuid}”)`);
5213
5408
  const resources = ensureArray(websiteTree.items.resource);
5214
5409
  const items = [...parseWebpages(resources), ...parseSegments(resources)];
5215
5410
  const sidebar = parseSidebar(resources);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ochre-sdk",
3
- "version": "0.22.19",
3
+ "version": "0.22.21",
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",
@@ -46,7 +46,7 @@
46
46
  "@date-fns/utc": "^2.1.1",
47
47
  "date-fns": "^4.1.0",
48
48
  "fast-equals": "^6.0.0",
49
- "zod": "^4.3.6"
49
+ "zod": "^4.4.1"
50
50
  },
51
51
  "devDependencies": {
52
52
  "@antfu/eslint-config": "^8.2.0",
@@ -54,9 +54,9 @@
54
54
  "bumpp": "^11.0.1",
55
55
  "eslint": "^10.2.1",
56
56
  "prettier": "^3.8.3",
57
- "tsdown": "^0.21.9",
57
+ "tsdown": "^0.21.10",
58
58
  "typescript": "^6.0.3",
59
- "vitest": "^4.1.4"
59
+ "vitest": "^4.1.5"
60
60
  },
61
61
  "scripts": {
62
62
  "dev": "tsdown src/index.ts --watch",