ochre-sdk 0.20.5 → 0.20.7

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
@@ -1231,6 +1231,12 @@ declare function fetchSetItems<U extends Array<DataCategory> = Array<DataCategor
1231
1231
  }>;
1232
1232
  //#endregion
1233
1233
  //#region src/utils/fetchers/set/property-values-by-property-variables.d.ts
1234
+ type SetPropertyValuesByPropertyVariablesTitleQueryInput = {
1235
+ value: string;
1236
+ matchMode: "includes" | "exact";
1237
+ isCaseSensitive: boolean;
1238
+ language?: string;
1239
+ };
1234
1240
  /**
1235
1241
  * Fetches and parses Set property values by property variables from the OCHRE API
1236
1242
  *
@@ -1238,6 +1244,7 @@ declare function fetchSetItems<U extends Array<DataCategory> = Array<DataCategor
1238
1244
  * @param params.setScopeUuids - An array of set scope UUIDs to filter by
1239
1245
  * @param params.belongsToCollectionScopeUuids - The collection scope UUIDs to filter by
1240
1246
  * @param params.propertyVariableUuids - The property variable UUIDs to query by
1247
+ * @param params.titleQuery - Title query to filter returned items by item title
1241
1248
  * @param params.isLimitedToLeafPropertyValues - Whether to limit the property values to leaf property values
1242
1249
  * @param options - Options for the fetch
1243
1250
  * @param options.fetch - The fetch function to use
@@ -1248,15 +1255,18 @@ declare function fetchSetPropertyValuesByPropertyVariables(params: {
1248
1255
  setScopeUuids: Array<string>;
1249
1256
  belongsToCollectionScopeUuids: Array<string>;
1250
1257
  propertyVariableUuids: Array<string>;
1258
+ titleQuery?: SetPropertyValuesByPropertyVariablesTitleQueryInput;
1251
1259
  isLimitedToLeafPropertyValues?: boolean;
1252
1260
  }, options?: {
1253
1261
  fetch?: (input: string | URL | globalThis.Request, init?: RequestInit) => Promise<Response>;
1254
1262
  version?: ApiVersion;
1255
1263
  }): Promise<{
1256
1264
  propertyValues: Array<PropertyValueQueryItem> | null;
1265
+ propertyValuesByPropertyVariableUuid: Record<string, Array<PropertyValueQueryItem>> | null;
1257
1266
  error: null;
1258
1267
  } | {
1259
1268
  propertyValues: null;
1269
+ propertyValuesByPropertyVariableUuid: null;
1260
1270
  error: string;
1261
1271
  }>;
1262
1272
  //#endregion
package/dist/index.mjs CHANGED
@@ -767,6 +767,12 @@ const setPropertyValuesByPropertyVariablesParamsSchema = z.object({
767
767
  setScopeUuids: z.array(uuidSchema).min(1, "At least one set scope UUID is required"),
768
768
  belongsToCollectionScopeUuids: z.array(uuidSchema).default([]),
769
769
  propertyVariableUuids: z.array(uuidSchema).min(1, "At least one property variable UUID is required"),
770
+ titleQuery: z.object({
771
+ value: z.string(),
772
+ matchMode: z.enum(["includes", "exact"]),
773
+ isCaseSensitive: z.boolean(),
774
+ language: z.string().default("eng")
775
+ }).strict().optional(),
770
776
  isLimitedToLeafPropertyValues: z.boolean().default(false)
771
777
  });
772
778
  const setItemsParamsSchema = z.object({
@@ -2109,7 +2115,7 @@ async function fetchItem(uuid, category, itemCategories, options) {
2109
2115
  * @param params.isCaseSensitive - Whether to match case-sensitively
2110
2116
  * @returns The string match predicate
2111
2117
  */
2112
- function buildStringMatchPredicate(params) {
2118
+ function buildStringMatchPredicate$1(params) {
2113
2119
  const { path, value, matchMode, isCaseSensitive } = params;
2114
2120
  const comparedPath = isCaseSensitive ? path : `lower-case(${path})`;
2115
2121
  const comparedValueLiteral = stringLiteral(isCaseSensitive ? value : value.toLowerCase());
@@ -2138,7 +2144,7 @@ function buildPropertyValuePredicate(query) {
2138
2144
  to: query.to
2139
2145
  })}]`;
2140
2146
  if (query.dataType === "time" || query.dataType === "integer" || query.dataType === "decimal" || query.dataType === "boolean") return `.//properties//property[value[@rawValue=${stringLiteral(query.value)}]]`;
2141
- return `.//properties//property[${buildStringMatchPredicate({
2147
+ return `.//properties//property[${buildStringMatchPredicate$1({
2142
2148
  path: `string-join(value/content[@xml:lang="${query.language}"]/string, "")`,
2143
2149
  value: query.value,
2144
2150
  matchMode: query.matchMode,
@@ -2152,31 +2158,31 @@ function buildPropertyValuePredicate(query) {
2152
2158
  */
2153
2159
  function buildQueryPredicate(query) {
2154
2160
  switch (query.target) {
2155
- case "title": return buildStringMatchPredicate({
2161
+ case "title": return buildStringMatchPredicate$1({
2156
2162
  path: `string-join(identification/label/content[@xml:lang="${query.language}"]/string, "")`,
2157
2163
  value: query.value,
2158
2164
  matchMode: query.matchMode,
2159
2165
  isCaseSensitive: query.isCaseSensitive
2160
2166
  });
2161
- case "description": return buildStringMatchPredicate({
2167
+ case "description": return buildStringMatchPredicate$1({
2162
2168
  path: `string-join(description/content[@xml:lang="${query.language}"]/string, "")`,
2163
2169
  value: query.value,
2164
2170
  matchMode: query.matchMode,
2165
2171
  isCaseSensitive: query.isCaseSensitive
2166
2172
  });
2167
- case "periods": return buildStringMatchPredicate({
2173
+ case "periods": return buildStringMatchPredicate$1({
2168
2174
  path: `string-join(periods/period/identification/label/content[@xml:lang="${query.language}"]/string, "")`,
2169
2175
  value: query.value,
2170
2176
  matchMode: query.matchMode,
2171
2177
  isCaseSensitive: query.isCaseSensitive
2172
2178
  });
2173
- case "bibliography": return buildStringMatchPredicate({
2179
+ case "bibliography": return buildStringMatchPredicate$1({
2174
2180
  path: `string-join(bibliographies/bibliography/identification/label/content[@xml:lang="${query.language}"]/string, "")`,
2175
2181
  value: query.value,
2176
2182
  matchMode: query.matchMode,
2177
2183
  isCaseSensitive: query.isCaseSensitive
2178
2184
  });
2179
- case "image": return buildStringMatchPredicate({
2185
+ case "image": return buildStringMatchPredicate$1({
2180
2186
  path: `string-join(image/identification/label/content[@xml:lang="${query.language}"]/string, "")`,
2181
2187
  value: query.value,
2182
2188
  matchMode: query.matchMode,
@@ -2343,11 +2349,88 @@ async function fetchSetItems(params, itemCategories, options) {
2343
2349
 
2344
2350
  //#endregion
2345
2351
  //#region src/utils/fetchers/set/property-values-by-property-variables.ts
2352
+ function parsePropertyValueLabel(content) {
2353
+ if (content == null || content === "") return null;
2354
+ if (typeof content === "object") return parseStringContent({ content });
2355
+ return parseFakeString(content);
2356
+ }
2357
+ function parsePropertyValueBooleanContent(rawValue) {
2358
+ if (rawValue == null || rawValue === "") return null;
2359
+ if (typeof rawValue === "boolean") return rawValue;
2360
+ return rawValue.toString().toLocaleLowerCase("en-US") === "true";
2361
+ }
2362
+ function getPropertyValueGroupKey(value) {
2363
+ const contentKey = value.content == null ? "null" : `${typeof value.content}:${value.content.toLocaleString("en-US")}`;
2364
+ return `${value.dataType}|${contentKey}`;
2365
+ }
2366
+ function aggregatePropertyValues(values) {
2367
+ const groupedPropertyValuesMap = /* @__PURE__ */ new Map();
2368
+ for (const value of values) {
2369
+ const key = getPropertyValueGroupKey(value);
2370
+ const existing = groupedPropertyValuesMap.get(key);
2371
+ if (existing == null) {
2372
+ groupedPropertyValuesMap.set(key, {
2373
+ dataType: value.dataType,
2374
+ content: value.content,
2375
+ label: value.label,
2376
+ itemUuids: new Set([value.itemUuid])
2377
+ });
2378
+ continue;
2379
+ }
2380
+ existing.itemUuids.add(value.itemUuid);
2381
+ if (existing.label == null && value.label != null) existing.label = value.label;
2382
+ }
2383
+ const groupedPropertyValues = [];
2384
+ for (const group of groupedPropertyValuesMap.values()) {
2385
+ if (group.content == null) continue;
2386
+ groupedPropertyValues.push({
2387
+ count: group.itemUuids.size,
2388
+ dataType: group.dataType,
2389
+ content: group.content,
2390
+ label: group.label
2391
+ });
2392
+ }
2393
+ return groupedPropertyValues.toSorted((a, b) => {
2394
+ if (a.count !== b.count) return b.count - a.count;
2395
+ if (a.label !== b.label) return a.label?.localeCompare(b.label ?? "") ?? 0;
2396
+ return a.content?.toString().localeCompare(b.content?.toString() ?? "") ?? 0;
2397
+ });
2398
+ }
2399
+ /**
2400
+ * Build a string match predicate for an XQuery string
2401
+ * @param params - The parameters for the predicate
2402
+ * @param params.path - The path to the string
2403
+ * @param params.value - The value to match
2404
+ * @param params.matchMode - The match mode (includes or exact)
2405
+ * @param params.isCaseSensitive - Whether to match case-sensitively
2406
+ * @returns The string match predicate
2407
+ */
2408
+ function buildStringMatchPredicate(params) {
2409
+ const { path, value, matchMode, isCaseSensitive } = params;
2410
+ const comparedPath = isCaseSensitive ? path : `lower-case(${path})`;
2411
+ const comparedValueLiteral = stringLiteral(isCaseSensitive ? value : value.toLowerCase());
2412
+ if (matchMode === "includes") return `contains(${comparedPath}, ${comparedValueLiteral})`;
2413
+ return `${comparedPath} = ${comparedValueLiteral}`;
2414
+ }
2415
+ /**
2416
+ * Build a title predicate for an XQuery string
2417
+ * @param titleQuery - The title query
2418
+ * @returns The title predicate
2419
+ */
2420
+ function buildTitlePredicate(titleQuery) {
2421
+ return buildStringMatchPredicate({
2422
+ path: `string-join(identification/label/content[@xml:lang="${titleQuery.language}"]/string, "")`,
2423
+ value: titleQuery.value,
2424
+ matchMode: titleQuery.matchMode,
2425
+ isCaseSensitive: titleQuery.isCaseSensitive
2426
+ });
2427
+ }
2346
2428
  /**
2347
2429
  * Schema for a single property value query item in the OCHRE API response
2348
2430
  */
2349
2431
  const propertyValueQueryItemSchema = z.object({
2350
2432
  uuid: z.string(),
2433
+ variableUuid: z.string().optional(),
2351
2434
  itemUuid: z.string().optional(),
2352
2435
  dataType: z.string(),
2353
2436
  rawValue: fakeStringSchema.optional(),
@@ -2358,6 +2441,7 @@ const propertyValueQueryItemSchema = z.object({
2358
2441
  ]).optional()
2359
2442
  }).transform((val) => {
2360
2443
  const returnValue = {
2444
+ variableUuid: val.variableUuid != null && val.variableUuid !== "" ? val.variableUuid : null,
2361
2445
  itemUuid: val.itemUuid != null && val.itemUuid !== "" ? val.itemUuid : null,
2362
2446
  dataType: val.dataType,
2363
2447
  content: null,
@@ -2366,21 +2450,24 @@ const propertyValueQueryItemSchema = z.object({
2366
2450
  switch (val.dataType) {
2367
2451
  case "IDREF":
2368
2452
  returnValue.content = val.uuid !== "" ? val.uuid : null;
2369
- returnValue.label = val.content != null && val.content !== "" ? typeof val.content === "object" ? parseStringContent({ content: val.content }) : parseFakeString(val.content) : null;
2453
+ returnValue.label = parsePropertyValueLabel(val.content);
2370
2454
  break;
2371
2455
  case "integer":
2372
2456
  case "decimal":
2373
2457
  case "time":
2374
- returnValue.content = val.rawValue != null && val.rawValue !== "" ? Number(val.rawValue) : null;
2375
- returnValue.label = val.content != null && val.content !== "" ? val.content.toString() : null;
2458
+ if (val.rawValue != null && val.rawValue !== "") {
2459
+ const numericContent = Number(val.rawValue);
2460
+ returnValue.content = Number.isNaN(numericContent) ? null : numericContent;
2461
+ }
2462
+ returnValue.label = parsePropertyValueLabel(val.content);
2376
2463
  break;
2377
2464
  case "boolean":
2378
- returnValue.content = val.rawValue != null && val.rawValue !== "" ? Boolean(val.rawValue) : null;
2379
- returnValue.label = val.content != null && val.content !== "" ? val.content.toString() : null;
2465
+ returnValue.content = parsePropertyValueBooleanContent(val.rawValue);
2466
+ returnValue.label = parsePropertyValueLabel(val.content);
2380
2467
  break;
2381
2468
  default:
2382
2469
  returnValue.content = val.rawValue != null && val.rawValue !== "" ? val.rawValue.toString() : null;
2383
- returnValue.label = val.content != null && val.content !== "" ? val.content.toString() : null;
2470
+ returnValue.label = parsePropertyValueLabel(val.content);
2384
2471
  break;
2385
2472
  }
2386
2473
  return returnValue;
@@ -2395,6 +2482,7 @@ const responseSchema = z.object({ result: z.union([z.object({ ochre: z.object({
2395
2482
  * @param params.setScopeUuids - An array of set scope UUIDs to filter by
2396
2483
  * @param params.belongsToCollectionScopeUuids - An array of collection scope UUIDs to filter by
2397
2484
  * @param params.propertyVariableUuids - An array of property variable UUIDs to fetch
2485
+ * @param params.titleQuery - Title query to filter returned items by item title
2398
2486
  * @param params.isLimitedToLeafPropertyValues - Whether to limit the property values to leaf property values
2399
2487
  * @param options - Options for the fetch
2400
2488
  * @param options.version - The version of the OCHRE API to use
@@ -2402,20 +2490,28 @@ const responseSchema = z.object({ result: z.union([z.object({ ochre: z.object({
2402
2490
  */
2403
2491
  function buildXQuery(params, options) {
2404
2492
  const version = options?.version ?? DEFAULT_API_VERSION;
2405
- const { setScopeUuids, belongsToCollectionScopeUuids, propertyVariableUuids, isLimitedToLeafPropertyValues } = params;
2406
- let setScopeFilter = "";
2407
- if (setScopeUuids.length > 0) setScopeFilter = `/set[(${setScopeUuids.map((uuid) => `@uuid="${uuid}"`).join(" or ")})]/items`;
2408
- let collectionScopeFilter = "";
2409
- if (belongsToCollectionScopeUuids.length > 0) collectionScopeFilter = `//properties[property[label/@uuid="${BELONGS_TO_COLLECTION_UUID}" and value/(${belongsToCollectionScopeUuids.map((uuid) => `@uuid="${uuid}"`).join(" or ")})]]`;
2493
+ const { setScopeUuids, belongsToCollectionScopeUuids, propertyVariableUuids, titleQuery, isLimitedToLeafPropertyValues } = params;
2494
+ let setScopeFilter = "/set/items/*";
2495
+ if (setScopeUuids.length > 0) setScopeFilter = `/set[(${setScopeUuids.map((uuid) => `@uuid="${uuid}"`).join(" or ")})]/items/*`;
2410
2496
  const propertyVariableFilters = propertyVariableUuids.map((uuid) => `@uuid="${uuid}"`).join(" or ");
2411
- return `<ochre>{${`let $matching-props := ${version === 2 ? "doc()" : "input()"}/ochre
2497
+ const filterPredicates = [];
2498
+ if (belongsToCollectionScopeUuids.length > 0) {
2499
+ const belongsToCollectionScopeValues = belongsToCollectionScopeUuids.map((uuid) => `@uuid="${uuid}"`).join(" or ");
2500
+ filterPredicates.push(`.//properties[property[label/@uuid="${BELONGS_TO_COLLECTION_UUID}" and value/(${belongsToCollectionScopeValues})]]`);
2501
+ }
2502
+ if (titleQuery != null) filterPredicates.push(buildTitlePredicate(titleQuery));
2503
+ const itemFilters = filterPredicates.length > 0 ? `[${filterPredicates.join(" and ")}]` : "";
2504
+ return `<ochre>{${`let $items := ${version === 2 ? "doc()" : "input()"}/ochre
2412
2505
  ${setScopeFilter}
2413
- ${collectionScopeFilter}
2414
- //property[label/(${propertyVariableFilters})]
2506
+ ${itemFilters}
2415
2507
 
2416
- for $v in $matching-props/value${isLimitedToLeafPropertyValues ? "[not(@i)]" : ""}
2508
+ let $matching-props := $items//property[label/(${propertyVariableFilters})]
2509
+
2510
+ for $p in $matching-props
2511
+ for $v in $p/value${isLimitedToLeafPropertyValues ? "[not(@i)]" : ""}
2417
2512
  let $item-uuid := $v/ancestor::*[parent::items]/@uuid
2418
- return <propertyValue uuid="{$v/@uuid}" rawValue="{$v/@rawValue}" dataType="{$v/@dataType}" itemUuid="{$item-uuid}">{
2513
+ let $variable-uuid := $p/label/@uuid
2514
+ return <propertyValue uuid="{$v/@uuid}" rawValue="{$v/@rawValue}" dataType="{$v/@dataType}" itemUuid="{$item-uuid}" variableUuid="{$variable-uuid}">{
2419
2515
  if ($v/content) then string-join($v/content[@xml:lang="eng"]/string, "") else $v/text()
2420
2516
  }</propertyValue>`}}</ochre>`;
2421
2517
  }
@@ -2426,6 +2522,7 @@ function buildXQuery(params, options) {
2426
2522
  * @param params.setScopeUuids - An array of set scope UUIDs to filter by
2427
2523
  * @param params.belongsToCollectionScopeUuids - The collection scope UUIDs to filter by
2428
2524
  * @param params.propertyVariableUuids - The property variable UUIDs to query by
2525
+ * @param params.titleQuery - Title query to filter returned items by item title
2429
2526
  * @param params.isLimitedToLeafPropertyValues - Whether to limit the property values to leaf property values
2430
2527
  * @param options - Options for the fetch
2431
2528
  * @param options.fetch - The fetch function to use
@@ -2435,11 +2532,12 @@ function buildXQuery(params, options) {
2435
2532
  async function fetchSetPropertyValuesByPropertyVariables(params, options) {
2436
2533
  try {
2437
2534
  const version = options?.version ?? DEFAULT_API_VERSION;
2438
- const { setScopeUuids, belongsToCollectionScopeUuids, propertyVariableUuids, isLimitedToLeafPropertyValues } = setPropertyValuesByPropertyVariablesParamsSchema.parse(params);
2535
+ const { setScopeUuids, belongsToCollectionScopeUuids, propertyVariableUuids, titleQuery, isLimitedToLeafPropertyValues } = setPropertyValuesByPropertyVariablesParamsSchema.parse(params);
2439
2536
  const xquery = buildXQuery({
2440
2537
  setScopeUuids,
2441
2538
  belongsToCollectionScopeUuids,
2442
2539
  propertyVariableUuids,
2540
+ titleQuery,
2443
2541
  isLimitedToLeafPropertyValues
2444
2542
  }, { version });
2445
2543
  const response = await (options?.fetch ?? fetch)(version === 2 ? `https://ochre.lib.uchicago.edu/ochre/v2/ochre.php?xquery=${encodeURIComponent(xquery)}&format=json&lang="*"` : `https://ochre.lib.uchicago.edu/ochre?xquery=${encodeURIComponent(xquery)}&format=json&lang="*"`);
@@ -2448,36 +2546,34 @@ async function fetchSetPropertyValuesByPropertyVariables(params, options) {
2448
2546
  const parsedResultRaw = responseSchema.parse(data);
2449
2547
  if (Array.isArray(parsedResultRaw.result)) throw new TypeError("No property values found");
2450
2548
  const parsedPropertyValues = Array.isArray(parsedResultRaw.result.ochre.propertyValue) ? parsedResultRaw.result.ochre.propertyValue : [parsedResultRaw.result.ochre.propertyValue];
2451
- const groupedPropertyValuesMap = /* @__PURE__ */ new Map();
2549
+ const propertyValuesByPropertyVariableUuidRaw = {};
2550
+ const flattenedPropertyValues = [];
2452
2551
  for (const propertyValue of parsedPropertyValues) {
2453
- const existing = groupedPropertyValuesMap.get(propertyValue.content);
2454
- if (existing == null) groupedPropertyValuesMap.set(propertyValue.content, {
2552
+ const aggregatePropertyValueItem = {
2553
+ itemUuid: propertyValue.itemUuid,
2455
2554
  dataType: propertyValue.dataType,
2456
2555
  content: propertyValue.content,
2457
- label: propertyValue.label,
2458
- itemUuids: new Set([propertyValue.itemUuid])
2459
- });
2460
- else existing.itemUuids.add(propertyValue.itemUuid);
2556
+ label: propertyValue.label
2557
+ };
2558
+ flattenedPropertyValues.push(aggregatePropertyValueItem);
2559
+ if (propertyValue.variableUuid == null) continue;
2560
+ (propertyValuesByPropertyVariableUuidRaw[propertyValue.variableUuid] ??= []).push(aggregatePropertyValueItem);
2561
+ }
2562
+ const propertyValuesByPropertyVariableUuid = {};
2563
+ for (const [propertyVariableUuid, values] of Object.entries(propertyValuesByPropertyVariableUuidRaw)) {
2564
+ const aggregatedValues = aggregatePropertyValues(values);
2565
+ if (aggregatedValues.length > 0) propertyValuesByPropertyVariableUuid[propertyVariableUuid] = aggregatedValues;
2461
2566
  }
2462
- const groupedPropertyValues = [];
2463
- for (const group of groupedPropertyValuesMap.values()) groupedPropertyValues.push({
2464
- count: group.itemUuids.size,
2465
- dataType: group.dataType,
2466
- content: group.content,
2467
- label: group.label
2468
- });
2469
2567
  return {
2470
- propertyValues: groupedPropertyValues.filter((propertyValue) => propertyValue.content !== null).toSorted((a, b) => {
2471
- if (a.count !== b.count) return b.count - a.count;
2472
- if (a.label !== b.label) return a.label?.localeCompare(b.label ?? "") ?? 0;
2473
- return a.content?.toString().localeCompare(b.content?.toString() ?? "") ?? 0;
2474
- }),
2568
+ propertyValues: aggregatePropertyValues(flattenedPropertyValues),
2569
+ propertyValuesByPropertyVariableUuid,
2475
2570
  error: null
2476
2571
  };
2477
2572
  } catch (error) {
2478
2573
  console.error(error);
2479
2574
  return {
2480
2575
  propertyValues: null,
2576
+ propertyValuesByPropertyVariableUuid: null,
2481
2577
  error: error instanceof Error ? error.message : "Failed to fetch property values by property variables"
2482
2578
  };
2483
2579
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ochre-sdk",
3
- "version": "0.20.5",
3
+ "version": "0.20.7",
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",