ochre-sdk 1.0.78 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. package/README.md +31 -3
  2. package/dist/_virtual/_rolldown/runtime.mjs +13 -0
  3. package/dist/categories.d.mts +249 -0
  4. package/dist/categories.mjs +259 -0
  5. package/dist/constants.d.mts +14 -0
  6. package/dist/constants.mjs +18 -1
  7. package/dist/errors.d.mts +23 -0
  8. package/dist/{utilities.mjs → errors.mjs} +33 -84
  9. package/dist/fetchers/gallery.mjs +15 -51
  10. package/dist/fetchers/item-children.mjs +20 -66
  11. package/dist/fetchers/item-links.mjs +26 -79
  12. package/dist/fetchers/item-ocr-data.d.mts +2 -2
  13. package/dist/fetchers/item-ocr-data.mjs +11 -15
  14. package/dist/fetchers/item.d.mts +0 -15
  15. package/dist/fetchers/item.mjs +20 -72
  16. package/dist/fetchers/request.d.mts +70 -0
  17. package/dist/fetchers/request.mjs +100 -0
  18. package/dist/fetchers/set/items.mjs +27 -73
  19. package/dist/fetchers/set/property-values.d.mts +2 -3
  20. package/dist/fetchers/set/property-values.mjs +96 -130
  21. package/dist/fetchers/website-metadata.mjs +35 -57
  22. package/dist/fetchers/website.d.mts +2 -3
  23. package/dist/fetchers/website.mjs +22 -31
  24. package/dist/getters.d.mts +78 -148
  25. package/dist/getters.mjs +127 -208
  26. package/dist/helpers.d.mts +0 -4
  27. package/dist/helpers.mjs +19 -6
  28. package/dist/index.d.mts +8 -6
  29. package/dist/index.mjs +6 -4
  30. package/dist/ocr.d.mts +37 -0
  31. package/dist/ocr.mjs +52 -0
  32. package/dist/parsers/helpers.d.mts +21 -1
  33. package/dist/parsers/helpers.mjs +26 -6
  34. package/dist/parsers/index.d.mts +0 -8
  35. package/dist/parsers/index.mjs +118 -259
  36. package/dist/parsers/languages.d.mts +72 -0
  37. package/dist/parsers/languages.mjs +132 -0
  38. package/dist/parsers/multilingual.d.mts +49 -74
  39. package/dist/parsers/multilingual.mjs +88 -189
  40. package/dist/parsers/property-token.d.mts +34 -0
  41. package/dist/parsers/property-token.mjs +29 -0
  42. package/dist/parsers/string.d.mts +19 -0
  43. package/dist/parsers/string.mjs +45 -25
  44. package/dist/parsers/website/bounds.d.mts +10 -0
  45. package/dist/parsers/website/bounds.mjs +28 -0
  46. package/dist/parsers/website/components.d.mts +91 -0
  47. package/dist/parsers/website/components.mjs +681 -0
  48. package/dist/parsers/website/index.d.mts +0 -7
  49. package/dist/parsers/website/index.mjs +92 -1153
  50. package/dist/parsers/website/links.d.mts +36 -0
  51. package/dist/parsers/website/links.mjs +58 -0
  52. package/dist/parsers/website/messages.d.mts +24 -0
  53. package/dist/parsers/website/messages.mjs +31 -0
  54. package/dist/parsers/website/options.d.mts +6 -0
  55. package/dist/parsers/website/options.mjs +114 -0
  56. package/dist/parsers/website/properties.d.mts +12 -0
  57. package/dist/parsers/website/properties.mjs +158 -0
  58. package/dist/parsers/website/reader.d.mts +54 -4
  59. package/dist/parsers/website/reader.mjs +65 -20
  60. package/dist/parsers/website/slug.d.mts +64 -0
  61. package/dist/parsers/website/slug.mjs +82 -0
  62. package/dist/parsers/website/styles.d.mts +28 -0
  63. package/dist/parsers/website/styles.mjs +103 -0
  64. package/dist/parsers/website/walk.d.mts +68 -0
  65. package/dist/parsers/website/walk.mjs +116 -0
  66. package/dist/query.d.mts +66 -18
  67. package/dist/query.mjs +202 -48
  68. package/dist/reflection.d.mts +64 -0
  69. package/dist/reflection.mjs +79 -0
  70. package/dist/schemas.d.mts +7 -0
  71. package/dist/schemas.mjs +12 -3
  72. package/dist/types/index.d.mts +1 -31
  73. package/dist/types/utilities.d.mts +9 -0
  74. package/dist/types/utilities.mjs +1 -0
  75. package/dist/types/website.d.mts +49 -58
  76. package/dist/xml/metadata.d.mts +16 -0
  77. package/dist/xml/metadata.mjs +32 -11
  78. package/dist/xml/schemas.d.mts +5970 -3
  79. package/dist/xml/schemas.mjs +43 -45
  80. package/dist/xml/types.d.mts +13 -30
  81. package/dist/xquery.d.mts +46 -0
  82. package/dist/xquery.mjs +66 -0
  83. package/package.json +3 -3
  84. package/dist/utilities.d.mts +0 -54
@@ -1,11 +1,12 @@
1
- import { BELONGS_TO_COLLECTION_UUID, DEFAULT_LANGUAGES, XML_PARSER_OPTIONS } from "../../constants.mjs";
2
- import { NOT_SUPPLEMENTAL_PREDICATE, createSchemaValidationError, getErrorOutput, stringLiteral } from "../../utilities.mjs";
1
+ import { DEFAULT_LANGUAGES } from "../../constants.mjs";
2
+ import { getErrorOutput } from "../../errors.mjs";
3
+ import { requestOchre } from "../request.mjs";
3
4
  import { MultilingualString } from "../../parsers/multilingual.mjs";
4
5
  import { setPropertyValuesParametersSchema } from "../../schemas.mjs";
5
6
  import { parseXMLContent } from "../../parsers/string.mjs";
6
- import { buildBelongsToCollectionQueryExpression, buildQueryPlan } from "../../query.mjs";
7
+ import { stringLiteral } from "../../xquery.mjs";
8
+ import { compileSetItemsQuery, getItemFilterQueries, getPropertyFacetSelectors } from "../../query.mjs";
7
9
  import * as v from "valibot";
8
- import { XMLParser } from "fast-xml-parser";
9
10
  //#region src/fetchers/set/property-values.ts
10
11
  function getLabelContentLanguages(content) {
11
12
  const languages = [];
@@ -22,12 +23,16 @@ function parsePropertyValueLabel(content, text) {
22
23
  if (parsedText == null) return null;
23
24
  return MultilingualString.fromObject({ [DEFAULT_LANGUAGES[0]]: parsedText });
24
25
  }
25
- function parsePropertyValueBooleanContent(rawValue) {
26
- if (rawValue === "" || rawValue == null) return null;
27
- return rawValue.toLocaleLowerCase("en-US") === "true";
28
- }
26
+ /**
27
+ * The prefix OCHRE puts on every `dataType` attribute
28
+ *
29
+ * Kept as a constant because the facet XQuery has to strip the same prefix
30
+ * server-side: it compares `@dataType` against the unprefixed names, so a
31
+ * prefixed value falls through every typed branch.
32
+ */
33
+ const XML_SCHEMA_TYPE_PREFIX = "xs:";
29
34
  function normalizePropertyValueDataType(dataType) {
30
- const normalizedDataType = dataType.startsWith("xs:") ? dataType.slice(3) : dataType;
35
+ const normalizedDataType = dataType.startsWith(XML_SCHEMA_TYPE_PREFIX) ? dataType.slice(3) : dataType;
31
36
  switch (normalizedDataType) {
32
37
  case "IDREF":
33
38
  case "boolean":
@@ -68,42 +73,26 @@ const propertyValueLabelContentSchema = v.object({
68
73
  lang: v.string(),
69
74
  string: v.array(propertyValueLabelStringSchema)
70
75
  });
71
- function getPropertyFacetSelectorsFromQueries(queries) {
72
- if (queries == null) return [];
73
- const propertyFacetSelectors = /* @__PURE__ */ new Map();
74
- const pendingQueries = [queries];
75
- while (pendingQueries.length > 0) {
76
- const query = pendingQueries.shift();
77
- if (query == null) continue;
78
- if ("target" in query) {
79
- if (query.target !== "property") continue;
80
- if (query.propertyVariable != null) {
81
- const relation = query.propertyRelation ?? null;
82
- propertyFacetSelectors.set(`${query.propertyVariable}|${relation}`, {
83
- uuid: query.propertyVariable,
84
- relation
85
- });
86
- }
87
- continue;
76
+ /**
77
+ * Give the canonical value the type its `dataType` implies
78
+ *
79
+ * Which value a facet stands for is decided once, server-side, by
80
+ * `local:value-content`, and travels back as `canonicalValue`. This only gives
81
+ * that string a TypeScript type: re-deriving it here from `rawValue`, `uuid`
82
+ * and the label is what let the facet key and the facet content disagree.
83
+ */
84
+ function typePropertyValueContent(dataType, canonicalValue) {
85
+ if (canonicalValue == null || canonicalValue === "") return null;
86
+ switch (dataType) {
87
+ case "integer":
88
+ case "decimal":
89
+ case "time": {
90
+ const numericContent = Number(canonicalValue);
91
+ return Number.isNaN(numericContent) ? null : numericContent;
88
92
  }
89
- pendingQueries.push(..."and" in query ? query.and : query.or);
90
- }
91
- return propertyFacetSelectors.values().toArray();
92
- }
93
- function getItemFilterQueriesFromPropertyValueQueries(queries) {
94
- if (queries == null) return null;
95
- if ("target" in queries) {
96
- if (queries.target !== "property" || queries.dataType === "date" || queries.dataType === "dateTime") return queries;
97
- return "value" in queries && queries.value != null ? queries : null;
98
- }
99
- const filteredChildren = [];
100
- const childQueries = "and" in queries ? queries.and : queries.or;
101
- for (const childQuery of childQueries) {
102
- const filteredChildQuery = getItemFilterQueriesFromPropertyValueQueries(childQuery);
103
- if (filteredChildQuery != null) filteredChildren.push(filteredChildQuery);
93
+ case "boolean": return canonicalValue === "true";
94
+ default: return canonicalValue;
104
95
  }
105
- if (filteredChildren.length <= 1) return filteredChildren[0] ?? null;
106
- return "and" in queries ? { and: filteredChildren } : { or: filteredChildren };
107
96
  }
108
97
  /**
109
98
  * Schema for a single property value query item in the OCHRE API response
@@ -115,44 +104,22 @@ const propertyValueQueryItemSchema = v.pipe(v.object({
115
104
  count: countSchema,
116
105
  globalCount: v.nullish(countSchema),
117
106
  dataType: v.optional(v.string(), "string"),
107
+ canonicalValue: v.optional(v.string()),
118
108
  rawValue: v.optional(v.string()),
119
109
  payload: v.optional(v.string()),
120
110
  content: v.optional(v.array(propertyValueLabelContentSchema))
121
111
  }), v.transform((value) => {
122
112
  const dataType = normalizePropertyValueDataType(value.dataType);
123
- const label = parsePropertyValueLabel(value.content, value.payload);
124
- const returnValue = {
113
+ return {
125
114
  uuid: value.uuid !== "" ? value.uuid : null,
126
115
  scope: value.scope,
127
116
  variableUuid: value.variableUuid != null && value.variableUuid !== "" ? value.variableUuid : null,
128
117
  count: value.count,
129
118
  globalCount: value.globalCount ?? null,
130
119
  dataType,
131
- content: null,
132
- label
120
+ content: typePropertyValueContent(dataType, value.canonicalValue),
121
+ label: parsePropertyValueLabel(value.content, value.payload)
133
122
  };
134
- switch (dataType) {
135
- case "IDREF":
136
- returnValue.content = value.uuid !== "" ? value.uuid : null;
137
- break;
138
- case "integer":
139
- case "decimal":
140
- case "time":
141
- if (value.rawValue != null && value.rawValue !== "") {
142
- const numericContent = Number(value.rawValue);
143
- returnValue.content = Number.isNaN(numericContent) ? null : numericContent;
144
- }
145
- break;
146
- case "boolean":
147
- returnValue.content = parsePropertyValueBooleanContent(value.rawValue);
148
- break;
149
- default: {
150
- const labelText = label?.getText() ?? null;
151
- returnValue.content = value.rawValue != null && value.rawValue !== "" ? value.rawValue : labelText;
152
- break;
153
- }
154
- }
155
- return returnValue;
156
123
  }));
157
124
  const attributeValueQueryItemSchema = v.pipe(v.object({
158
125
  attributeType: v.picklist(["bibliographies", "periods"]),
@@ -187,20 +154,9 @@ const responseSchema = v.object({ result: v.object({ ochre: v.object({
187
154
  */
188
155
  function buildXQuery(parameters) {
189
156
  const { setScopeUuids, belongsToCollectionScopeUuids, queries, propertyFacetSelectors, attributes, isLimitedToLeafPropertyValues } = parameters;
190
- const setScopeDeclaration = `declare variable $setScopeUuids := (${setScopeUuids.map((uuid) => stringLiteral(uuid)).join(", ")});`;
191
- const compiledQueryPlan = buildQueryPlan({
192
- queries: getItemFilterQueriesFromPropertyValueQueries(queries),
193
- baseItemsExpression: "doc()/ochre/set[@uuid = $setScopeUuids]/items/*",
194
- scopeQueryExpression: buildBelongsToCollectionQueryExpression(belongsToCollectionScopeUuids, BELONGS_TO_COLLECTION_UUID)
195
- });
196
157
  const valueFilter = isLimitedToLeafPropertyValues ? "[not(@i)]" : "";
197
- const queryBlocks = [];
198
158
  const returnedSequences = [];
199
- const xqueryDeclarations = [
200
- "xquery version \"1.0-ml\";",
201
- "declare namespace map = \"http://marklogic.com/xdmp/map\";",
202
- setScopeDeclaration,
203
- `declare function local:increment-count($counts, $key) {
159
+ const xqueryDeclarations = ["declare namespace map = \"http://marklogic.com/xdmp/map\";", `declare function local:increment-count($counts, $key) {
204
160
  let $current := map:get($counts, $key)
205
161
  return map:put(
206
162
  $counts,
@@ -209,6 +165,12 @@ function buildXQuery(parameters) {
209
165
  )
210
166
  };
211
167
 
168
+ declare function local:normalize-data-type($data-type) {
169
+ if (starts-with($data-type, ${stringLiteral(XML_SCHEMA_TYPE_PREFIX)}))
170
+ then substring($data-type, 4)
171
+ else $data-type
172
+ };
173
+
212
174
  declare function local:value-display-text($v) {
213
175
  if ($v/content)
214
176
  then string-join($v/content[@xml:lang="eng"]//text(), "")
@@ -257,6 +219,7 @@ declare function local:put-property-detail(
257
219
  $value-uuid,
258
220
  $raw-value,
259
221
  $data-type,
222
+ $canonical-value,
260
223
  $display,
261
224
  $label-content
262
225
  ) {
@@ -274,7 +237,7 @@ declare function local:put-property-detail(
274
237
  map:put(
275
238
  $details,
276
239
  $key,
277
- <propertyValue scope="{$scope}" variableUuid="{$variable-uuid}" uuid="{$value-uuid}" rawValue="{$raw-value}" dataType="{$data-type}">{
240
+ <propertyValue scope="{$scope}" variableUuid="{$variable-uuid}" uuid="{$value-uuid}" rawValue="{$raw-value}" dataType="{$data-type}" canonicalValue="{$canonical-value}">{
278
241
  if (exists($label-content)) then $label-content else $display
279
242
  }</propertyValue>
280
243
  )
@@ -291,6 +254,7 @@ declare function local:add-property-facet(
291
254
  $value-uuid,
292
255
  $raw-value,
293
256
  $data-type,
257
+ $canonical-value,
294
258
  $display,
295
259
  $label-content
296
260
  ) {
@@ -298,7 +262,7 @@ declare function local:add-property-facet(
298
262
  else (
299
263
  map:put($seen, $key, true()),
300
264
  local:increment-count($counts, $key),
301
- local:put-property-detail($details, $key, $scope, $variable-uuid, $value-uuid, $raw-value, $data-type, $display, $label-content)
265
+ local:put-property-detail($details, $key, $scope, $variable-uuid, $value-uuid, $raw-value, $data-type, $canonical-value, $display, $label-content)
302
266
  )
303
267
  };
304
268
 
@@ -308,31 +272,32 @@ declare function local:add-attribute-facet($counts, $seen, $key) {
308
272
  map:put($seen, $key, true()),
309
273
  local:increment-count($counts, $key)
310
274
  )
311
- };`
312
- ];
313
- if (compiledQueryPlan.prolog !== "") xqueryDeclarations.push(compiledQueryPlan.prolog);
314
- if (propertyFacetSelectors.length > 0) {
315
- const facetPropertyPredicates = [];
316
- for (const selector of propertyFacetSelectors) {
317
- const uuidPredicate = `label/@uuid = ${stringLiteral(selector.uuid)}`;
318
- facetPropertyPredicates.push(selector.relation == null ? uuidPredicate : `(${uuidPredicate} and label/@relation = ${stringLiteral(selector.relation)})`);
319
- }
320
- const facetPropertyPredicate = facetPropertyPredicates.length === 1 ? facetPropertyPredicates[0] ?? "false()" : `(${facetPropertyPredicates.join(" or ")})`;
321
- queryBlocks.push(`let $global-property-counts := map:map()
275
+ };`];
276
+ function buildQueryBlocks(context) {
277
+ const { items, notSupplemental } = context;
278
+ const queryBlocks = [];
279
+ if (propertyFacetSelectors.length > 0) {
280
+ const facetPropertyPredicates = [];
281
+ for (const selector of propertyFacetSelectors) {
282
+ const uuidPredicate = `label/@uuid = ${stringLiteral(selector.uuid)}`;
283
+ facetPropertyPredicates.push(selector.relation == null ? uuidPredicate : `(${uuidPredicate} and label/@relation = ${stringLiteral(selector.relation)})`);
284
+ }
285
+ const facetPropertyPredicate = facetPropertyPredicates.length === 1 ? facetPropertyPredicates[0] ?? "false()" : `(${facetPropertyPredicates.join(" or ")})`;
286
+ queryBlocks.push(`let $global-property-counts := map:map()
322
287
  let $variable-property-counts := map:map()
323
288
  let $variable-property-details := map:map()
324
289
  let $variable-property-global-keys := map:map()
325
290
  let $_property-aggregation := xdmp:eager(
326
- for $item in $items
291
+ for $item in ${items}
327
292
  let $global-seen := map:map()
328
293
  let $variable-seen := map:map()
329
294
  return
330
- for $p in $item/properties/property[${facetPropertyPredicate}]${NOT_SUPPLEMENTAL_PREDICATE}
295
+ for $p in $item/properties/property[${facetPropertyPredicate}]${notSupplemental}
331
296
  let $variable-uuid := string($p/label/@uuid)
332
- for $v in $p/value${valueFilter}${NOT_SUPPLEMENTAL_PREDICATE}
297
+ for $v in $p/value${valueFilter}${notSupplemental}
333
298
  let $value-uuid := string($v/@uuid)
334
299
  let $raw-value := string($v/@rawValue)
335
- let $data-type := string($v/@dataType)
300
+ let $data-type := local:normalize-data-type(string($v/@dataType))
336
301
  let $display := local:value-display-text($v)
337
302
  let $label-content := local:value-label-content($v)
338
303
  let $content := local:value-content($data-type, $raw-value, $value-uuid, $display)
@@ -343,7 +308,7 @@ let $_property-aggregation := xdmp:eager(
343
308
  where $content != ""
344
309
  return (
345
310
  local:add-attribute-facet($global-property-counts, $global-seen, $global-key),
346
- 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, $label-content),
311
+ local:add-property-facet($variable-property-counts, $variable-property-details, $variable-seen, $variable-key, "variable", $variable-uuid, $value-uuid, $output-raw-value, $data-type, $content, $display, $label-content),
347
312
  map:put($variable-property-global-keys, $variable-key, $global-key)
348
313
  )
349
314
  )
@@ -354,19 +319,19 @@ let $property-values :=
354
319
  for $key in map:keys($variable-property-counts)
355
320
  let $detail := map:get($variable-property-details, $key)
356
321
  let $global-key := map:get($variable-property-global-keys, $key)
357
- 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)}">{
322
+ return <propertyValue scope="variable" variableUuid="{string($detail/@variableUuid)}" uuid="{string($detail/@uuid)}" rawValue="{string($detail/@rawValue)}" dataType="{string($detail/@dataType)}" canonicalValue="{string($detail/@canonicalValue)}" count="{map:get($variable-property-counts, $key)}" globalCount="{map:get($global-property-counts, $global-key)}">{
358
323
  $detail/node()
359
324
  }</propertyValue>
360
325
  )`);
361
- returnedSequences.push("$property-values");
362
- }
363
- if (attributes.bibliographies) {
364
- queryBlocks.push(`let $bibliography-counts := map:map()
326
+ returnedSequences.push("$property-values");
327
+ }
328
+ if (attributes.bibliographies) {
329
+ queryBlocks.push(`let $bibliography-counts := map:map()
365
330
  let $_bibliography-aggregation := xdmp:eager(
366
- for $item in $items
331
+ for $item in ${items}
367
332
  let $seen := map:map()
368
333
  return
369
- for $bibliography in $item/bibliographies/bibliography${NOT_SUPPLEMENTAL_PREDICATE}
334
+ for $bibliography in $item/bibliographies/bibliography${notSupplemental}
370
335
  let $label := string-join($bibliography/identification/label/content[@xml:lang="eng"]//text(), "")
371
336
  where string-length($label) gt 0
372
337
  return local:add-attribute-facet($bibliography-counts, $seen, $label)
@@ -378,15 +343,15 @@ let $bibliography-values :=
378
343
  for $label in map:keys($bibliography-counts)
379
344
  return <attributeValue attributeType="bibliographies" count="{map:get($bibliography-counts, $label)}" content="{$label}" />
380
345
  )`);
381
- returnedSequences.push("$bibliography-values");
382
- }
383
- if (attributes.periods) {
384
- queryBlocks.push(`let $period-counts := map:map()
346
+ returnedSequences.push("$bibliography-values");
347
+ }
348
+ if (attributes.periods) {
349
+ queryBlocks.push(`let $period-counts := map:map()
385
350
  let $_period-aggregation := xdmp:eager(
386
- for $item in $items
351
+ for $item in ${items}
387
352
  let $seen := map:map()
388
353
  return
389
- for $period in $item/periods/period${NOT_SUPPLEMENTAL_PREDICATE}
354
+ for $period in $item/periods/period${notSupplemental}
390
355
  let $label := string-join($period/identification/label/content[@xml:lang="eng"]//text(), "")
391
356
  where string-length($label) gt 0
392
357
  return local:add-attribute-facet($period-counts, $seen, $label)
@@ -398,16 +363,21 @@ let $period-values :=
398
363
  for $label in map:keys($period-counts)
399
364
  return <attributeValue attributeType="periods" count="{map:get($period-counts, $label)}" content="{$label}" />
400
365
  )`);
401
- returnedSequences.push("$period-values");
366
+ returnedSequences.push("$period-values");
367
+ }
368
+ return queryBlocks;
402
369
  }
403
- return `${xqueryDeclarations.join("\n\n")}
404
-
405
- <ochre>{
406
- ${compiledQueryPlan.itemsClause}
407
- ${queryBlocks.join("\n\n")}
370
+ return compileSetItemsQuery({
371
+ setScopeUuids,
372
+ belongsToCollectionScopeUuids,
373
+ queries: getItemFilterQueries(queries),
374
+ declarations: xqueryDeclarations,
375
+ body: (context) => {
376
+ return `${buildQueryBlocks(context).join("\n\n")}
408
377
 
409
- return (${returnedSequences.join(", ")})
410
- }</ochre>`;
378
+ return (${returnedSequences.join(", ")})`;
379
+ }
380
+ });
411
381
  }
412
382
  function collectPropertyValues(parsedPropertyValues) {
413
383
  const propertyValuesByPropertyVariableUuid = {};
@@ -476,7 +446,7 @@ function collectAttributeValues(parsedAttributeValues) {
476
446
  async function fetchSetPropertyValues(parameters, options) {
477
447
  try {
478
448
  const { setScopeUuids, belongsToCollectionScopeUuids, queries, attributes, isLimitedToLeafPropertyValues } = v.parse(setPropertyValuesParametersSchema, parameters);
479
- const propertyFacetSelectors = getPropertyFacetSelectorsFromQueries(queries);
449
+ const propertyFacetSelectors = getPropertyFacetSelectors(queries);
480
450
  if (propertyFacetSelectors.length === 0 && !attributes.bibliographies && !attributes.periods) return {
481
451
  propertyValues: [],
482
452
  propertyValuesByPropertyVariableUuid: {},
@@ -495,16 +465,12 @@ async function fetchSetPropertyValues(parameters, options) {
495
465
  attributes,
496
466
  isLimitedToLeafPropertyValues
497
467
  });
498
- const response = await (options?.fetch ?? fetch)("https://ochre.lib.uchicago.edu/ochre/v2/ochre.php?xquery&xsl=none&lang=\"*\"", {
499
- method: "POST",
500
- body: xquery,
501
- headers: { "Content-Type": "application/xquery" }
468
+ const output = await requestOchre({
469
+ xquery,
470
+ schema: responseSchema,
471
+ label: "OCHRE Set property values",
472
+ options
502
473
  });
503
- if (!response.ok) throw new Error(`OCHRE API responded with status: ${response.status}`, { cause: response.statusText });
504
- const dataRaw = await response.text();
505
- const data = new XMLParser(XML_PARSER_OPTIONS).parse(dataRaw);
506
- const { success, issues, output } = v.safeParse(responseSchema, data);
507
- if (!success) throw createSchemaValidationError("Failed to parse OCHRE Set property values", issues);
508
474
  const parsedPropertyValues = [output.result.ochre.propertyValue ?? []].flat();
509
475
  const parsedAttributeValues = [output.result.ochre.attributeValue ?? []].flat();
510
476
  const { propertyValuesByPropertyVariableUuid, flattenedPropertyValues } = collectPropertyValues(parsedPropertyValues);
@@ -1,14 +1,26 @@
1
- import { XML_PARSER_OPTIONS } from "../constants.mjs";
2
- import { SUPPLEMENTAL_XQUERY_PROLOG, createSchemaValidationError, getErrorOutput, omitSupplemental, stringLiteral } from "../utilities.mjs";
3
- import { iso639_3Schema } from "../schemas.mjs";
4
- import { restoreXMLMetadata } from "../xml/metadata.mjs";
1
+ import { getErrorOutput } from "../errors.mjs";
2
+ import { requestOchre } from "./request.mjs";
5
3
  import { parseStringLike } from "../parsers/helpers.mjs";
6
- import { parseIdentification, parseMetadataLanguages, parseSimplifiedProperties, resolveLanguages } from "../parsers/index.mjs";
4
+ import { parseMetadataLanguages, parseRequestedLanguages, resolveLanguages } from "../parsers/languages.mjs";
5
+ import { parseIdentification, parseSimplifiedProperties } from "../parsers/index.mjs";
7
6
  import { XMLWebsiteData } from "../xml/schemas.mjs";
7
+ import { compileOchreQuery, stringLiteral } from "../xquery.mjs";
8
8
  import { websitePresentationReader } from "../parsers/website/reader.mjs";
9
- import * as v from "valibot";
10
- import { XMLParser } from "fast-xml-parser";
9
+ import { WEBSITE_WALK_DECLARATIONS } from "../parsers/website/walk.mjs";
11
10
  //#region src/fetchers/website-metadata.ts
11
+ /**
12
+ * The presentation properties the metadata projection carries
13
+ *
14
+ * The XQuery whitelists exactly these labels and the parser reads exactly
15
+ * these labels, so they come from one list: whitelisting a label the parser
16
+ * ignores is dead weight, and reading one the whitelist drops silently yields
17
+ * the default.
18
+ */
19
+ const METADATA_PRESENTATION_LABELS = {
20
+ privacy: "privacy",
21
+ faviconIco: "favicon-ico",
22
+ faviconImg: "favicon-img"
23
+ };
12
24
  function parseWebsiteMetadata(data, options) {
13
25
  const rawOchre = data.result.ochre;
14
26
  const websiteTree = rawOchre.tree[0];
@@ -30,52 +42,21 @@ function parseWebsiteMetadata(data, options) {
30
42
  description: websiteName === "" ? metadataDescription : metadataDescription !== "" && metadataDescription !== websiteName ? `${websiteName} - ${metadataDescription}` : websiteName,
31
43
  webpageTitle,
32
44
  properties: {
33
- privacy: reader.valueOr("privacy", "public"),
45
+ privacy: reader.valueOr(METADATA_PRESENTATION_LABELS.privacy, "public"),
34
46
  icon: {
35
- faviconUuid: reader.uuid("favicon-ico"),
36
- appleTouchIconUuid: reader.uuid("favicon-img")
47
+ faviconUuid: reader.uuid(METADATA_PRESENTATION_LABELS.faviconIco),
48
+ appleTouchIconUuid: reader.uuid(METADATA_PRESENTATION_LABELS.faviconImg)
37
49
  }
38
50
  }
39
51
  };
40
52
  }
41
53
  function buildXQuery(parameters) {
42
- return String.raw`xquery version "1.0-ml";
43
-
44
- ${SUPPLEMENTAL_XQUERY_PROLOG}
45
-
46
- declare function local:resource-items($resources) {
47
- for $resource in $resources
48
- return
49
- if ($resource/segments) then $resource
50
- else if ($resource/identification) then $resource
51
- else local:resource-items($resource/resource)
52
- };
53
-
54
- declare function local:presentation($resource) {
55
- string(($resource/properties/property[label/string() = "presentation"]/value)[1])
56
- };
57
-
58
- declare function local:clean-slug($slug) {
59
- replace(string($slug), "^\$[^-]*-", "")
60
- };
61
-
62
- declare function local:page-slug($resource, $slug-prefix) {
63
- let $slug := local:clean-slug($resource/@slug)
64
- return
65
- if ($slug-prefix = "") then $slug
66
- else if ($slug = "") then $slug-prefix
67
- else concat($slug-prefix, "/", $slug)
68
- };
69
-
54
+ return compileOchreQuery({
55
+ declarations: [`${WEBSITE_WALK_DECLARATIONS}
70
56
  declare function local:matches-page-slug($resource, $target-slug, $slug-prefix) {
71
57
  local:page-slug($resource, $slug-prefix) = $target-slug
72
58
  };
73
59
 
74
- declare function local:page-child-slug-prefix($resource, $slug-prefix) {
75
- if ($slug-prefix = "") then ""
76
- else local:page-slug($resource, $slug-prefix)
77
- };
78
-
79
60
  declare function local:metadata-page($resource) {
80
61
  element resource {
81
62
  attribute uuid { string($resource/@uuid) },
@@ -108,7 +89,7 @@ declare function local:matching-pages($resources, $target-slug, $slug-prefix) {
108
89
 
109
90
  declare function local:metadata-tree($tree, $target-slug, $slug-prefix) {
110
91
  let $resources := local:resource-items($tree/items/resource)
111
- let $presentation-properties := $tree/properties/property[label/string() = "presentation"][value/string() = "website"]/property[label/string() = ("favicon-ico", "favicon-img", "privacy")]
92
+ let $presentation-properties := $tree/properties/property[label/string() = "presentation"][value/string() = "website"]/property[label/string() = (${Object.values(METADATA_PRESENTATION_LABELS).map((label) => stringLiteral(label)).join(", ")})]
112
93
  return
113
94
  element tree {
114
95
  attribute uuid { string($tree/@uuid) },
@@ -124,7 +105,8 @@ declare function local:metadata-tree($tree, $target-slug, $slug-prefix) {
124
105
  }
125
106
  };
126
107
 
127
- let $website := collection("ochre/tree")/ochre[tree/identification/abbreviation/content/string = ${stringLiteral(parameters.abbreviation)}][1]
108
+ `],
109
+ body: ({ omitSupplemental }) => `let $website := collection("ochre/tree")/ochre[tree/identification/abbreviation/content/string = ${stringLiteral(parameters.abbreviation)}][1]
128
110
  let $target-slug := ${stringLiteral(parameters.slug)}
129
111
  return
130
112
  <ochre>{
@@ -137,28 +119,24 @@ return
137
119
  $website/metadata,
138
120
  local:metadata-tree($website/tree[1], $target-slug, "")
139
121
  )`)}
140
- }</ochre>`;
122
+ }</ochre>`
123
+ });
141
124
  }
142
125
  async function fetchWebsiteMetadata(abbreviation, options) {
143
126
  try {
144
127
  if (options?.slug == null) throw new Error("Website metadata slug is required");
145
128
  const cleanAbbreviation = abbreviation.trim().toLocaleLowerCase("en-US");
146
129
  const slug = options.slug.trim().replaceAll(/^\/+|\/+$/g, "");
147
- const requestedLanguages = Array.from(options.languages ?? [], (language) => v.parse(iso639_3Schema, language));
148
- const response = await (options.fetch ?? fetch)("https://ochre.lib.uchicago.edu/ochre/v2/ochre.php?xquery&xsl=none&lang=\"*\"", {
149
- method: "POST",
150
- body: buildXQuery({
130
+ const requestedLanguages = parseRequestedLanguages(options.languages);
131
+ const output = await requestOchre({
132
+ xquery: buildXQuery({
151
133
  abbreviation: cleanAbbreviation,
152
134
  slug
153
135
  }),
154
- headers: { "Content-Type": "application/xquery" }
136
+ schema: XMLWebsiteData,
137
+ label: "OCHRE website metadata",
138
+ options
155
139
  });
156
- if (!response.ok) throw new Error("Failed to fetch website metadata", { cause: response.statusText });
157
- const dataRaw = await response.text();
158
- const data = new XMLParser(XML_PARSER_OPTIONS).parse(dataRaw);
159
- const { success, issues, output } = v.safeParse(XMLWebsiteData, data);
160
- if (!success) throw createSchemaValidationError("Failed to parse website metadata XML", issues);
161
- restoreXMLMetadata(output, data);
162
140
  const metadataLanguages = parseMetadataLanguages(output.result.ochre);
163
141
  return {
164
142
  websiteMetadata: parseWebsiteMetadata(output, { languages: resolveLanguages(requestedLanguages, metadataLanguages) }),
@@ -1,6 +1,6 @@
1
1
  import { ProtectedWebsite, Website } from "../types/website.mjs";
2
2
  import { LanguageCodes } from "../types/index.mjs";
3
- import { FetchFunction } from "../parsers/helpers.mjs";
3
+ import { OchreRequestOptions } from "./request.mjs";
4
4
  //#region src/fetchers/website.d.ts
5
5
  /**
6
6
  * Fetches and parses a website configuration from the OCHRE API.
@@ -11,8 +11,7 @@ import { FetchFunction } from "../parsers/helpers.mjs";
11
11
  * `username` and `password` for OCHRE accounts) to authenticate and receive the full
12
12
  * website data.
13
13
  */
14
- export declare function fetchWebsite<const T extends LanguageCodes = LanguageCodes>(abbreviation: string, options?: {
15
- fetch?: FetchFunction;
14
+ export declare function fetchWebsite<const T extends LanguageCodes = LanguageCodes>(abbreviation: string, options?: OchreRequestOptions & {
16
15
  languages?: T;
17
16
  credentials?: string | {
18
17
  username: string;
@@ -1,24 +1,23 @@
1
- import { XML_PARSER_OPTIONS } from "../constants.mjs";
2
- import { SUPPLEMENTAL_XQUERY_PROLOG, createSchemaValidationError, getErrorOutput, omitSupplemental, stringLiteral } from "../utilities.mjs";
3
- import { restoreXMLMetadata } from "../xml/metadata.mjs";
1
+ import { getErrorOutput } from "../errors.mjs";
2
+ import { isOchreJsonAccepted, requestOchre } from "./request.mjs";
3
+ import { parseLanguages } from "../parsers/languages.mjs";
4
4
  import { XMLWebsiteData } from "../xml/schemas.mjs";
5
+ import { compileOchreQuery, stringLiteral } from "../xquery.mjs";
5
6
  import { parseWebsite } from "../parsers/website/index.mjs";
6
- import * as v from "valibot";
7
- import { XMLParser } from "fast-xml-parser";
8
7
  //#region src/fetchers/website.ts
9
- async function areWebsiteCredentialsValid(uuid, credentials, fetcher) {
8
+ async function areWebsiteCredentialsValid(uuid, credentials, options) {
10
9
  const security = typeof credentials === "string" ? { validate: credentials } : {
11
10
  validate: credentials.password,
12
11
  userOCHRE: credentials.username
13
12
  };
14
- return (await fetcher("https://ochre.lib.uchicago.edu/ochre/v2/ochre.php", {
15
- method: "POST",
16
- body: JSON.stringify({
13
+ return isOchreJsonAccepted({
14
+ body: {
17
15
  uuid,
18
16
  data: { security }
19
- }),
20
- headers: { "Content-Type": "application/json" }
21
- })).ok;
17
+ },
18
+ label: "website credentials",
19
+ options
20
+ });
22
21
  }
23
22
  /**
24
23
  * Build an XQuery string to fetch a website tree document by abbreviation.
@@ -27,12 +26,8 @@ async function areWebsiteCredentialsValid(uuid, credentials, fetcher) {
27
26
  * @returns An XQuery string
28
27
  */
29
28
  function buildXQuery(abbreviation) {
30
- return `xquery version "1.0-ml";
31
-
32
- ${SUPPLEMENTAL_XQUERY_PROLOG}
33
-
34
- for $ochre in collection("ochre/tree")/ochre[tree/identification/abbreviation/content/string = ${stringLiteral(abbreviation)}]
35
- return element ochre { $ochre/@*, ${omitSupplemental("$ochre/node()")} }`;
29
+ return compileOchreQuery({ body: ({ omitSupplemental }) => `for $ochre in collection("ochre/tree")/ochre[tree/identification/abbreviation/content/string = ${stringLiteral(abbreviation)}]
30
+ return element ochre { $ochre/@*, ${omitSupplemental("$ochre/node()")} }` });
36
31
  }
37
32
  /**
38
33
  * Fetches and parses a website configuration from the OCHRE API.
@@ -45,19 +40,15 @@ return element ochre { $ochre/@*, ${omitSupplemental("$ochre/node()")} }`;
45
40
  */
46
41
  async function fetchWebsite(abbreviation, options) {
47
42
  try {
48
- const fetcher = options?.fetch ?? fetch;
49
- const response = await fetcher("https://ochre.lib.uchicago.edu/ochre/v2/ochre.php?xquery&xsl=none&lang=\"*\"", {
50
- method: "POST",
51
- body: buildXQuery(abbreviation.trim().toLocaleLowerCase("en-US")),
52
- headers: { "Content-Type": "application/xquery" }
43
+ const cleanAbbreviation = abbreviation.trim().toLocaleLowerCase("en-US");
44
+ const languages = options?.languages == null ? void 0 : parseLanguages(options.languages);
45
+ const output = await requestOchre({
46
+ xquery: buildXQuery(cleanAbbreviation),
47
+ schema: XMLWebsiteData,
48
+ label: "OCHRE website",
49
+ options
53
50
  });
54
- if (!response.ok) throw new Error("Failed to fetch website", { cause: response.statusText });
55
- const dataRaw = await response.text();
56
- const data = new XMLParser(XML_PARSER_OPTIONS).parse(dataRaw);
57
- const { success, issues, output } = v.safeParse(XMLWebsiteData, data);
58
- if (!success) throw createSchemaValidationError("Failed to parse website XML", issues);
59
- restoreXMLMetadata(output, data);
60
- const website = parseWebsite(output, { languages: options?.languages });
51
+ const website = parseWebsite(output, { languages });
61
52
  if (website.properties.privacy !== "public") {
62
53
  if (options?.credentials == null) return {
63
54
  website: null,
@@ -69,7 +60,7 @@ async function fetchWebsite(abbreviation, options) {
69
60
  error: null,
70
61
  detailedError: null
71
62
  };
72
- if (!await areWebsiteCredentialsValid(website.uuid, options.credentials, fetcher)) throw new Error("Invalid credentials for protected website");
63
+ if (!await areWebsiteCredentialsValid(website.uuid, options.credentials, options)) throw new Error("Invalid credentials for protected website");
73
64
  }
74
65
  return {
75
66
  website,