ochre-sdk 1.0.12 → 1.0.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/README.md +3 -1
  2. package/dist/constants.d.mts +17 -0
  3. package/dist/constants.mjs +85 -0
  4. package/dist/fetchers/gallery.d.mts +38 -0
  5. package/dist/fetchers/gallery.mjs +91 -0
  6. package/dist/fetchers/item-links.d.mts +32 -0
  7. package/dist/fetchers/item-links.mjs +120 -0
  8. package/dist/fetchers/item.d.mts +74 -0
  9. package/dist/fetchers/item.mjs +146 -0
  10. package/dist/fetchers/set/items.d.mts +48 -0
  11. package/dist/fetchers/set/items.mjs +268 -0
  12. package/dist/fetchers/set/property-values.d.mts +46 -0
  13. package/dist/fetchers/set/property-values.mjs +514 -0
  14. package/dist/fetchers/website.d.mts +25 -0
  15. package/dist/fetchers/website.mjs +38 -0
  16. package/dist/getters.d.mts +193 -0
  17. package/dist/getters.mjs +341 -0
  18. package/dist/helpers.d.mts +18 -0
  19. package/dist/helpers.mjs +33 -0
  20. package/dist/index.d.mts +12 -1966
  21. package/dist/index.mjs +9 -7201
  22. package/dist/parsers/helpers.d.mts +27 -0
  23. package/dist/parsers/helpers.mjs +53 -0
  24. package/dist/parsers/index.d.mts +65 -0
  25. package/dist/parsers/index.mjs +1338 -0
  26. package/dist/parsers/mdx.d.mts +4 -0
  27. package/dist/parsers/mdx.mjs +9 -0
  28. package/dist/parsers/multilingual.d.mts +189 -0
  29. package/dist/parsers/multilingual.mjs +410 -0
  30. package/dist/parsers/string.d.mts +29 -0
  31. package/dist/parsers/string.mjs +477 -0
  32. package/dist/parsers/website/index.d.mts +20 -0
  33. package/dist/parsers/website/index.mjs +1245 -0
  34. package/dist/parsers/website/reader.d.mts +29 -0
  35. package/dist/parsers/website/reader.mjs +75 -0
  36. package/dist/query.d.mts +13 -0
  37. package/dist/query.mjs +827 -0
  38. package/dist/schemas.d.mts +84 -0
  39. package/dist/schemas.mjs +232 -0
  40. package/dist/types/index.d.mts +840 -0
  41. package/dist/types/index.mjs +1 -0
  42. package/dist/types/website.d.mts +501 -0
  43. package/dist/types/website.mjs +1 -0
  44. package/dist/utils.d.mts +34 -0
  45. package/dist/utils.mjs +172 -0
  46. package/dist/xml/metadata.d.mts +5 -0
  47. package/dist/xml/metadata.mjs +30 -0
  48. package/dist/xml/schemas.d.mts +13 -0
  49. package/dist/xml/schemas.mjs +849 -0
  50. package/dist/xml/types.d.mts +901 -0
  51. package/dist/xml/types.mjs +1 -0
  52. package/package.json +19 -17
@@ -0,0 +1,48 @@
1
+ import { Query, SetItem, SetItemCategory, SetItemsSort } from "../../types/index.mjs";
2
+
3
+ //#region src/fetchers/set/items.d.ts
4
+ type FetchFunction = (input: string | URL | globalThis.Request, init?: RequestInit) => Promise<Response>;
5
+ type FetchSetItemsBaseOptions<TLanguages extends ReadonlyArray<string> | undefined = undefined> = {
6
+ languages?: TLanguages;
7
+ fetch?: FetchFunction;
8
+ };
9
+ type FetchSetItemsLanguages<TLanguages extends ReadonlyArray<string> | undefined> = TLanguages extends readonly [] ? ReadonlyArray<string> : TLanguages extends ReadonlyArray<string> ? TLanguages : ReadonlyArray<string>;
10
+ type FetchSetItemsCategory<TContainedItemCategories extends ReadonlyArray<SetItemCategory> | undefined> = TContainedItemCategories extends ReadonlyArray<infer U> ? Extract<U, SetItemCategory> : SetItemCategory;
11
+ /**
12
+ * Fetches and parses Set items from the OCHRE API
13
+ *
14
+ * @param params - The parameters for the fetch
15
+ * @param params.setScopeUuids - The Set scope UUIDs to filter by
16
+ * @param params.queries - Recursive query tree used to filter matching items
17
+ * @param params.sort - Optional sorting configuration applied before pagination.
18
+ * For propertyValue sorting, dataType is required and the sort key uses the first valid leaf value (value[not(@i)]).
19
+ * @param params.page - The page number (1-indexed)
20
+ * @param params.pageSize - The number of items per page
21
+ * @param containedItemCategories - The categories of the items to fetch
22
+ * @param options - Options for the fetch
23
+ * @param options.fetch - The fetch function to use
24
+ * @returns The parsed Set items or null if the fetch/parse fails
25
+ */
26
+ declare function fetchSetItems<const TContainedItemCategories extends ReadonlyArray<SetItemCategory> | undefined = undefined, const TLanguages extends ReadonlyArray<string> | undefined = undefined>(params: {
27
+ setScopeUuids: Array<string>;
28
+ queries?: Query | null;
29
+ sort?: SetItemsSort;
30
+ page: number;
31
+ pageSize?: number;
32
+ }, containedItemCategories?: TContainedItemCategories, options?: FetchSetItemsBaseOptions<TLanguages>): Promise<{
33
+ totalCount: number;
34
+ page: number;
35
+ pageSize: number;
36
+ items: Array<SetItem<FetchSetItemsCategory<TContainedItemCategories>, FetchSetItemsLanguages<TLanguages>>>;
37
+ error: null;
38
+ detailedError: null;
39
+ } | {
40
+ totalCount: null;
41
+ page: null;
42
+ pageSize: null;
43
+ items: null;
44
+ error: string;
45
+ detailedError: string;
46
+ }>;
47
+ //#endregion
48
+ export { fetchSetItems };
@@ -0,0 +1,268 @@
1
+ import { BELONGS_TO_COLLECTION_UUID, DEFAULT_LANGUAGES, XML_PARSER_OPTIONS } from "../../constants.mjs";
2
+ import { createSchemaValidationError, getErrorOutput, stringLiteral } from "../../utils.mjs";
3
+ import { iso639_3Schema, setItemsParamsSchema } from "../../schemas.mjs";
4
+ import { restoreXMLMetadata } from "../../xml/metadata.mjs";
5
+ import { parseSetItems } from "../../parsers/index.mjs";
6
+ import { XMLSetItemsData } from "../../xml/schemas.mjs";
7
+ import { buildAndCtsQueryExpression, buildBelongsToCollectionQueryExpression, buildQueryPlan } from "../../query.mjs";
8
+ import * as v from "valibot";
9
+ import { XMLParser } from "fast-xml-parser";
10
+ //#region src/fetchers/set/items.ts
11
+ function parseLanguages(languages) {
12
+ const parsedLanguages = [];
13
+ for (const language of languages) parsedLanguages.push(v.parse(iso639_3Schema, language));
14
+ return parsedLanguages;
15
+ }
16
+ function isRecord(value) {
17
+ return typeof value === "object" && value !== null;
18
+ }
19
+ function collectContentLanguages(value, languages) {
20
+ if (Array.isArray(value)) {
21
+ for (const item of value) collectContentLanguages(item, languages);
22
+ return;
23
+ }
24
+ if (!isRecord(value)) return;
25
+ const content = value.content;
26
+ if (Array.isArray(content)) for (const contentItem of content) {
27
+ if (!isRecord(contentItem)) continue;
28
+ const language = contentItem.lang;
29
+ if (typeof language === "string" && language !== "zxx") languages.add(language);
30
+ }
31
+ for (const child of Object.values(value)) collectContentLanguages(child, languages);
32
+ }
33
+ function resolveSetItemsLanguages(data, requestedLanguages) {
34
+ if (requestedLanguages.length > 0) return requestedLanguages;
35
+ const languages = /* @__PURE__ */ new Set();
36
+ collectContentLanguages(data.result.ochre.items, languages);
37
+ return languages.size > 0 ? [...languages] : [...DEFAULT_LANGUAGES];
38
+ }
39
+ function hasArray(items) {
40
+ return items != null && items.length > 0;
41
+ }
42
+ function hasSetItemsCategory(items, category) {
43
+ switch (category) {
44
+ case "tree": return hasArray(items.tree);
45
+ case "bibliography": return hasArray(items.bibliography);
46
+ case "concept": return hasArray(items.concept);
47
+ case "spatialUnit": return hasArray(items.spatialUnit);
48
+ case "period": return hasArray(items.period);
49
+ case "person": return hasArray(items.person);
50
+ case "propertyVariable": return hasArray(items.propertyVariable) || hasArray(items.variable);
51
+ case "propertyValue": return hasArray(items.propertyValue) || hasArray(items.value);
52
+ case "resource": return hasArray(items.resource);
53
+ case "text": return hasArray(items.text);
54
+ case "set": return hasArray(items.set);
55
+ }
56
+ }
57
+ function mapSortDirectionToXQuery(direction) {
58
+ return direction === "desc" ? "descending" : "ascending";
59
+ }
60
+ function buildStringOrderByClause(direction) {
61
+ return `($sortKey = "") ascending, lower-case($sortKey) ${direction}, $position ascending`;
62
+ }
63
+ function buildTypedOrderByClause(direction) {
64
+ return `empty($sortKey) ascending, $sortKey ${direction}, $position ascending`;
65
+ }
66
+ function buildPropertyValueValuePath(sort) {
67
+ return `$item//properties//property[label/@uuid=${stringLiteral(sort.propertyVariableUuid)}]/value[not(@i)]`;
68
+ }
69
+ function buildPropertyValueStringSortKeyExpression(sort) {
70
+ const languageLiteral = stringLiteral(sort.language ?? "eng");
71
+ return `string((for $v in ${buildPropertyValueValuePath(sort)}
72
+ let $candidate := string-join($v/content[@xml:lang=${languageLiteral}]/string, "")
73
+ where string-length($candidate) gt 0
74
+ return $candidate)[1])`;
75
+ }
76
+ function buildPropertyValueTypedSortKeyExpression(params) {
77
+ const { sort, dataType } = params;
78
+ const propertyValuePath = buildPropertyValueValuePath(sort);
79
+ switch (dataType) {
80
+ case "integer": return `(for $v in ${propertyValuePath}
81
+ let $candidate := normalize-space(string($v/@rawValue))
82
+ where $candidate castable as xs:integer
83
+ return xs:integer($candidate))[1]`;
84
+ case "decimal":
85
+ case "time": return `(for $v in ${propertyValuePath}
86
+ let $candidate := normalize-space(string($v/@rawValue))
87
+ where $candidate castable as xs:decimal
88
+ return xs:decimal($candidate))[1]`;
89
+ case "boolean": return `(for $v in ${propertyValuePath}
90
+ let $candidate := lower-case(normalize-space(string($v/@rawValue)))
91
+ where $candidate = ("true", "false", "1", "0")
92
+ return if ($candidate = ("true", "1")) then 1 else 0)[1]`;
93
+ case "date": return `(for $v in ${propertyValuePath}
94
+ let $candidate := normalize-space(string($v/@rawValue))
95
+ where $candidate castable as xs:date
96
+ return xs:date($candidate))[1]`;
97
+ case "dateTime": return `(for $v in ${propertyValuePath}
98
+ let $candidate := normalize-space(string($v/@rawValue))
99
+ where $candidate castable as xs:dateTime
100
+ return xs:dateTime($candidate))[1]`;
101
+ }
102
+ }
103
+ function buildPropertyValueOrderByClause(params) {
104
+ const { dataType, direction } = params;
105
+ return dataType === "string" || dataType === "IDREF" ? buildStringOrderByClause(direction) : buildTypedOrderByClause(direction);
106
+ }
107
+ function buildOrderedItemsClause(sort) {
108
+ if (sort.target === "none") return "let $orderedItems := $items";
109
+ const direction = mapSortDirectionToXQuery(sort.direction);
110
+ if (sort.target === "title") return `let $orderedItems :=
111
+ for $item at $position in $items
112
+ let $sortKey := ${`string-join($item/identification/label/content[@xml:lang=${stringLiteral(sort.language ?? "eng")}]/string, "")`}
113
+ stable order by ${buildStringOrderByClause(direction)}
114
+ return $item`;
115
+ return `let $orderedItems :=
116
+ for $item at $position in $items
117
+ let $sortKey := ${sort.dataType === "string" || sort.dataType === "IDREF" ? buildPropertyValueStringSortKeyExpression(sort) : buildPropertyValueTypedSortKeyExpression({
118
+ sort,
119
+ dataType: sort.dataType
120
+ })}
121
+ stable order by ${buildPropertyValueOrderByClause({
122
+ dataType: sort.dataType,
123
+ direction
124
+ })}
125
+ return $item`;
126
+ }
127
+ function isExactStringPropertyQuery(query) {
128
+ return "target" in query && query.target === "property" && query.dataType === "string" && query.value != null && query.matchMode === "exact" && query.isNegated !== true;
129
+ }
130
+ function getCtsQueriesWithoutExactStringPropertyQueries(queries) {
131
+ if (queries == null) return null;
132
+ if ("target" in queries) return isExactStringPropertyQuery(queries) ? null : queries;
133
+ if ("or" in queries) return queries;
134
+ const filteredChildren = [];
135
+ for (const childQuery of queries.and) {
136
+ const filteredChildQuery = getCtsQueriesWithoutExactStringPropertyQueries(childQuery);
137
+ if (filteredChildQuery != null) filteredChildren.push(filteredChildQuery);
138
+ }
139
+ if (filteredChildren.length === 0) return null;
140
+ return filteredChildren.length === 1 ? filteredChildren[0] ?? null : { and: filteredChildren };
141
+ }
142
+ function buildExactStringPropertyPredicate(query) {
143
+ const propertyPredicates = [];
144
+ const value = stringLiteral(query.value);
145
+ if (query.propertyVariable != null) propertyPredicates.push(`label/@uuid = ${stringLiteral(query.propertyVariable)}`);
146
+ propertyPredicates.push(`value[
147
+ not(@inherited = "true")
148
+ and (
149
+ content[@xml:lang = ${stringLiteral(query.language)}]/string = ${value}
150
+ or @rawValue = ${value}
151
+ or (not(content) and text() = ${value})
152
+ )
153
+ ]`);
154
+ return `.//properties/property[${propertyPredicates.join(" and ")}]`;
155
+ }
156
+ function buildExactStringPropertyXPathFilterExpression(queries) {
157
+ if (queries == null) return null;
158
+ if ("target" in queries) return isExactStringPropertyQuery(queries) ? buildExactStringPropertyPredicate(queries) : null;
159
+ if ("or" in queries) return null;
160
+ const childExpressions = [];
161
+ for (const childQuery of queries.and) {
162
+ const childExpression = buildExactStringPropertyXPathFilterExpression(childQuery);
163
+ if (childExpression != null) childExpressions.push(childExpression);
164
+ }
165
+ if (childExpressions.length === 0) return null;
166
+ return childExpressions.join(" and ");
167
+ }
168
+ /**
169
+ * Build an XQuery string to fetch Set items from the OCHRE API
170
+ * @param params - The parameters for the fetch
171
+ * @param params.setScopeUuids - An array of Set scope UUIDs to filter by
172
+ * @param params.belongsToCollectionScopeUuids - An array of collection scope UUIDs to filter by
173
+ * @param params.queries - Recursive query tree used to filter matching items
174
+ * @param params.sort - Optional sorting configuration applied before pagination.
175
+ * For propertyValue sorting, dataType is required and the sort key uses the first valid leaf value (value[not(@i)]).
176
+ * @param params.page - The page number (1-indexed)
177
+ * @param params.pageSize - The number of items per page
178
+ * @returns An XQuery string
179
+ */
180
+ function buildXQuery(params) {
181
+ const { queries, sort, setScopeUuids, belongsToCollectionScopeUuids, page, pageSize } = params;
182
+ const startPosition = (page - 1) * pageSize + 1;
183
+ const setScopeDeclaration = `declare variable $setScopeUuids := (${setScopeUuids.map((uuid) => stringLiteral(uuid)).join(", ")});`;
184
+ const baseItemsExpression = "doc()/ochre/set[@uuid = $setScopeUuids]/items/*";
185
+ const ctsQueries = getCtsQueriesWithoutExactStringPropertyQueries(queries);
186
+ const exactStringPropertyXPathFilterExpression = buildExactStringPropertyXPathFilterExpression(queries);
187
+ const compiledQueryPlan = buildQueryPlan({ queries: ctsQueries });
188
+ const itemsQueryExpressions = [];
189
+ const belongsToCollectionQueryExpression = buildBelongsToCollectionQueryExpression(belongsToCollectionScopeUuids, BELONGS_TO_COLLECTION_UUID);
190
+ if (compiledQueryPlan.queryExpression != null) itemsQueryExpressions.push(compiledQueryPlan.queryExpression);
191
+ if (belongsToCollectionQueryExpression != null) itemsQueryExpressions.push(belongsToCollectionQueryExpression);
192
+ const itemsQueryExpression = buildAndCtsQueryExpression(itemsQueryExpressions);
193
+ const orderedItemsClause = buildOrderedItemsClause(sort);
194
+ const xqueryDeclarations = ["xquery version \"1.0-ml\";", setScopeDeclaration];
195
+ if (compiledQueryPlan.prolog !== "") xqueryDeclarations.push(compiledQueryPlan.prolog);
196
+ const searchedItemsClause = itemsQueryExpression == null ? `let $searchedItems := ${baseItemsExpression}` : `let $query := ${itemsQueryExpression}
197
+ let $searchedItems := cts:search(${baseItemsExpression}, $query)`;
198
+ const itemsClause = exactStringPropertyXPathFilterExpression == null ? `${searchedItemsClause}
199
+ let $items := $searchedItems` : `${searchedItemsClause}
200
+ let $items := $searchedItems[${exactStringPropertyXPathFilterExpression}]`;
201
+ return `${xqueryDeclarations.join("\n\n")}
202
+
203
+ <ochre>{
204
+ ${itemsClause}
205
+ let $totalCount := count($items)
206
+ ${orderedItemsClause}
207
+ let $pagedItems := subsequence($orderedItems, ${startPosition}, ${pageSize})
208
+
209
+ return <items totalCount="{$totalCount}" page="${page}" pageSize="${pageSize}">{
210
+ $pagedItems
211
+ }</items>
212
+ }</ochre>`;
213
+ }
214
+ async function fetchSetItems(params, containedItemCategories, options) {
215
+ try {
216
+ const { setScopeUuids, belongsToCollectionScopeUuids, queries, sort, page, pageSize } = v.parse(setItemsParamsSchema, params);
217
+ const requestedLanguages = options?.languages == null ? [] : parseLanguages(options.languages);
218
+ const xquery = buildXQuery({
219
+ setScopeUuids,
220
+ belongsToCollectionScopeUuids,
221
+ queries,
222
+ sort,
223
+ page,
224
+ pageSize
225
+ });
226
+ const response = await (options?.fetch ?? fetch)("https://ochre.lib.uchicago.edu/ochre/v2/ochre.php?xquery&xsl=none&lang=\"*\"", {
227
+ method: "POST",
228
+ body: xquery,
229
+ headers: { "Content-Type": "application/xquery" }
230
+ });
231
+ if (!response.ok) throw new Error(`OCHRE API responded with status: ${response.status}`, { cause: response.statusText });
232
+ const dataRaw = await response.text();
233
+ const data = new XMLParser(XML_PARSER_OPTIONS).parse(dataRaw);
234
+ const { success, issues, output } = v.safeParse(XMLSetItemsData, data);
235
+ if (!success) throw createSchemaValidationError("Failed to parse OCHRE Set items", issues);
236
+ restoreXMLMetadata(output, data);
237
+ if (containedItemCategories != null) {
238
+ const missingCategories = containedItemCategories.filter((category) => !hasSetItemsCategory(output.result.ochre.items, category));
239
+ if (missingCategories.length > 0) throw new Error(`No Set items found for item categories: ${missingCategories.join(", ")}`, { cause: missingCategories });
240
+ }
241
+ const languages = resolveSetItemsLanguages(output, requestedLanguages);
242
+ const items = parseSetItems(output.result.ochre.items, {
243
+ containedItemCategories,
244
+ languages
245
+ });
246
+ const itemsByUuid = /* @__PURE__ */ new Map();
247
+ for (const item of items) if (!itemsByUuid.has(item.uuid)) itemsByUuid.set(item.uuid, item);
248
+ const uniqueItems = [...itemsByUuid.values()];
249
+ return {
250
+ totalCount: output.result.ochre.items.totalCount,
251
+ page: output.result.ochre.items.page,
252
+ pageSize: output.result.ochre.items.pageSize,
253
+ items: uniqueItems,
254
+ error: null,
255
+ detailedError: null
256
+ };
257
+ } catch (error) {
258
+ return {
259
+ totalCount: null,
260
+ page: null,
261
+ pageSize: null,
262
+ items: null,
263
+ ...getErrorOutput(error, "Failed to fetch Set items")
264
+ };
265
+ }
266
+ }
267
+ //#endregion
268
+ export { fetchSetItems };
@@ -0,0 +1,46 @@
1
+ import { PropertyValueQueryItem, Query, SetAttributeValueQueryItem } from "../../types/index.mjs";
2
+
3
+ //#region src/fetchers/set/property-values.d.ts
4
+ /**
5
+ * Fetches and parses Set property values from the OCHRE API
6
+ *
7
+ * @param params - The parameters for the fetch
8
+ * @param params.setScopeUuids - An array of set scope UUIDs to filter by
9
+ * @param params.queries - Recursive query tree used to filter matching items
10
+ * @param params.attributes - Whether to return values for bibliographies and periods
11
+ * @param params.attributes.bibliographies - Whether to return values for bibliographies
12
+ * @param params.attributes.periods - Whether to return values for periods
13
+ * @param params.isLimitedToLeafPropertyValues - Whether to limit the property values to leaf property values
14
+ * @param options - Options for the fetch
15
+ * @param options.fetch - The fetch function to use
16
+ * @returns Parsed Set property values and requested attribute values.
17
+ * Returns empty arrays/objects when no matches are found, and null outputs on fetch/parse errors.
18
+ */
19
+ declare function fetchSetPropertyValues(params: {
20
+ setScopeUuids: Array<string>;
21
+ queries?: Query | null;
22
+ attributes?: {
23
+ bibliographies: boolean;
24
+ periods: boolean;
25
+ };
26
+ isLimitedToLeafPropertyValues?: boolean;
27
+ }, options?: {
28
+ fetch?: (input: string | URL | globalThis.Request, init?: RequestInit) => Promise<Response>;
29
+ }): Promise<{
30
+ propertyValues: Array<PropertyValueQueryItem>;
31
+ propertyValuesByPropertyVariableUuid: Record<string, Array<PropertyValueQueryItem>>;
32
+ attributeValues: {
33
+ bibliographies: Array<SetAttributeValueQueryItem> | null;
34
+ periods: Array<SetAttributeValueQueryItem> | null;
35
+ };
36
+ error: null;
37
+ detailedError: null;
38
+ } | {
39
+ propertyValues: null;
40
+ propertyValuesByPropertyVariableUuid: null;
41
+ attributeValues: null;
42
+ error: string;
43
+ detailedError: string;
44
+ }>;
45
+ //#endregion
46
+ export { fetchSetPropertyValues };