ochre-sdk 0.20.24 → 0.20.26

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
@@ -675,7 +675,7 @@ type SetItemsSort = {
675
675
  */
676
676
  type Query = {
677
677
  target: "property";
678
- propertyVariables: Array<string>;
678
+ propertyVariables?: Array<string>;
679
679
  dataType: Exclude<Exclude<PropertyValueContentType, "coordinate">, "date" | "dateTime">;
680
680
  propertyValues?: Array<string>;
681
681
  from?: never;
@@ -1268,9 +1268,9 @@ declare function fetchSetItems<U extends Array<DataCategory> = Array<DataCategor
1268
1268
  error: string;
1269
1269
  }>;
1270
1270
  //#endregion
1271
- //#region src/utils/fetchers/set/property-values-by-property-variables.d.ts
1271
+ //#region src/utils/fetchers/set/property-values.d.ts
1272
1272
  /**
1273
- * Fetches and parses Set property values by property variables from the OCHRE API
1273
+ * Fetches and parses Set property values from the OCHRE API
1274
1274
  *
1275
1275
  * @param params - The parameters for the fetch
1276
1276
  * @param params.setScopeUuids - An array of set scope UUIDs to filter by
@@ -1285,7 +1285,7 @@ declare function fetchSetItems<U extends Array<DataCategory> = Array<DataCategor
1285
1285
  * @returns Parsed Set property values and requested attribute values.
1286
1286
  * Returns empty arrays/objects when no matches are found, and null outputs on fetch/parse errors.
1287
1287
  */
1288
- declare function fetchSetPropertyValuesByPropertyVariables(params: {
1288
+ declare function fetchSetPropertyValues(params: {
1289
1289
  setScopeUuids: Array<string>;
1290
1290
  queries?: Array<Query>;
1291
1291
  attributes?: {
@@ -1470,4 +1470,4 @@ declare const DEFAULT_PAGE_SIZE = 48;
1470
1470
  */
1471
1471
  declare function flattenItemProperties<T extends DataCategory = DataCategory, U extends DataCategory | Array<DataCategory> = (T extends "tree" ? Exclude<DataCategory, "tree"> : T extends "set" ? Array<DataCategory> : never)>(item: Item<T, U>): Item<T, U>;
1472
1472
  //#endregion
1473
- export { ApiVersion, Bibliography, Concept, Context, ContextItem, ContextNode, Coordinate, DEFAULT_API_VERSION, DEFAULT_PAGE_SIZE, Data, DataCategory, Event, FileFormat, Gallery, Identification, Image, ImageMap, ImageMapArea, Interpretation, Item, LevelContext, LevelContextItem, License, Link, Metadata, Note, Observation, Period, Person, Property, PropertyContexts, PropertyValue, PropertyValueContent, PropertyValueContentType, PropertyValueQueryItem, PropertyVariable, Query, Resource, Scope, Section, Set, SetAttributeValueQueryItem, SetItemsSort, SetItemsSortDirection, SpatialUnit, Style, Text, Tree, WebBlock, WebBlockLayout, WebElement, WebElementComponent, WebImage, WebSegment, WebSegmentItem, WebTitle, Webpage, Website, WebsiteType, fetchGallery, fetchItem, fetchSetItems, fetchSetPropertyValuesByPropertyVariables, fetchWebsite, filterProperties, flattenItemProperties, getLeafPropertyValues, getPropertyByLabel, getPropertyByLabelAndValue, getPropertyByLabelAndValues, getPropertyByUuid, getPropertyValueByLabel, getPropertyValueByUuid, getPropertyValuesByLabel, getPropertyValuesByUuid, getUniqueProperties, getUniquePropertyLabels };
1473
+ export { ApiVersion, Bibliography, Concept, Context, ContextItem, ContextNode, Coordinate, DEFAULT_API_VERSION, DEFAULT_PAGE_SIZE, Data, DataCategory, Event, FileFormat, Gallery, Identification, Image, ImageMap, ImageMapArea, Interpretation, Item, LevelContext, LevelContextItem, License, Link, Metadata, Note, Observation, Period, Person, Property, PropertyContexts, PropertyValue, PropertyValueContent, PropertyValueContentType, PropertyValueQueryItem, PropertyVariable, Query, Resource, Scope, Section, Set, SetAttributeValueQueryItem, SetItemsSort, SetItemsSortDirection, SpatialUnit, Style, Text, Tree, WebBlock, WebBlockLayout, WebElement, WebElementComponent, WebImage, WebSegment, WebSegmentItem, WebTitle, Webpage, Website, WebsiteType, fetchGallery, fetchItem, fetchSetItems, fetchSetPropertyValues, fetchWebsite, filterProperties, flattenItemProperties, getLeafPropertyValues, getPropertyByLabel, getPropertyByLabelAndValue, getPropertyByLabelAndValues, getPropertyByUuid, getPropertyValueByLabel, getPropertyValueByUuid, getPropertyValuesByLabel, getPropertyValuesByUuid, getUniqueProperties, getUniquePropertyLabels };
package/dist/index.mjs CHANGED
@@ -759,7 +759,7 @@ const boundsSchema = z.string().transform((str, ctx) => {
759
759
  const setQuerySchema = z.union([
760
760
  z.object({
761
761
  target: z.literal("property"),
762
- propertyVariables: z.array(uuidSchema).min(1, "At least one property variable UUID is required"),
762
+ propertyVariables: z.array(uuidSchema).default([]),
763
763
  dataType: z.enum([
764
764
  "string",
765
765
  "integer",
@@ -774,7 +774,12 @@ const setQuerySchema = z.union([
774
774
  language: z.string().default("eng"),
775
775
  operator: z.enum(["AND", "OR"]).optional(),
776
776
  isNegated: z.boolean().optional().default(false)
777
- }).strict(),
777
+ }).strict().superRefine((value, ctx) => {
778
+ if (value.propertyVariables.length === 0 && value.propertyValues == null) ctx.addIssue({
779
+ code: "custom",
780
+ message: "Property queries must include at least one propertyVariable or propertyValue"
781
+ });
782
+ }),
778
783
  z.object({
779
784
  target: z.literal("property"),
780
785
  propertyVariables: z.array(uuidSchema).min(1, "At least one property variable UUID is required"),
@@ -861,10 +866,10 @@ function validateSetQueriesOperators(queries, ctx) {
861
866
  });
862
867
  }
863
868
  /**
864
- * Schema for validating the parameters for the Set property values by property variables fetching function
869
+ * Schema for validating the parameters for the Set property values fetching function
865
870
  * @internal
866
871
  */
867
- const setPropertyValuesByPropertyVariablesParamsSchema = z.object({
872
+ const setPropertyValuesParamsSchema = z.object({
868
873
  setScopeUuids: z.array(uuidSchema).min(1, "At least one set scope UUID is required"),
869
874
  belongsToCollectionScopeUuids: z.array(uuidSchema).default([]),
870
875
  queries: setQueriesSchema,
@@ -878,10 +883,10 @@ const setPropertyValuesByPropertyVariablesParamsSchema = z.object({
878
883
  isLimitedToLeafPropertyValues: z.boolean().default(false)
879
884
  }).superRefine((value, ctx) => {
880
885
  validateSetQueriesOperators(value.queries, ctx);
881
- if (!value.queries.some((query) => query.target === "property")) ctx.addIssue({
886
+ if (!value.queries.some((query) => query.target === "property" && query.propertyVariables.length > 0)) ctx.addIssue({
882
887
  code: "custom",
883
888
  path: ["queries"],
884
- message: "At least one property query is required"
889
+ message: "At least one property query with propertyVariables is required"
885
890
  });
886
891
  });
887
892
  const setItemsParamsSchema = z.object({
@@ -2145,10 +2150,35 @@ const CTS_INCLUDES_STOP_WORDS = [
2145
2150
  "to"
2146
2151
  ];
2147
2152
  const CTS_INCLUDES_STOP_WORDS_VAR = "$ctsIncludesStopWords";
2153
+ const CTS_INCLUDES_TOKEN_SPLIT_REGEX = /\W+/u;
2154
+ function tokenizeIncludesSearchValue(params) {
2155
+ const { value, isCaseSensitive } = params;
2156
+ const rawTerms = (isCaseSensitive ? value : value.toLowerCase()).split(CTS_INCLUDES_TOKEN_SPLIT_REGEX);
2157
+ const terms = [];
2158
+ for (const term of rawTerms) {
2159
+ const normalizedTerm = term.toLowerCase();
2160
+ if (normalizedTerm !== "" && !CTS_INCLUDES_STOP_WORDS.includes(normalizedTerm)) terms.push(term);
2161
+ }
2162
+ return terms;
2163
+ }
2148
2164
  function buildFlattenedContentValuesExpression(contentNodesExpression) {
2149
2165
  return `for $content in ${contentNodesExpression}
2150
2166
  return string-join($content//text(), "")`;
2151
2167
  }
2168
+ function buildSearchableContentNodesExpression(contentNodesExpression) {
2169
+ return `for $content in ${contentNodesExpression}
2170
+ return (
2171
+ $content//string[not(ancestor::string)],
2172
+ $content[not(.//string)]
2173
+ )`;
2174
+ }
2175
+ function buildCombinedSearchableContentNodesExpression(contentNodesExpressions) {
2176
+ const searchableExpressions = [];
2177
+ for (const contentNodesExpression of contentNodesExpressions) searchableExpressions.push(buildSearchableContentNodesExpression(contentNodesExpression));
2178
+ if (searchableExpressions.length === 0) return "()";
2179
+ if (searchableExpressions.length === 1) return searchableExpressions[0] ?? "()";
2180
+ return `(${searchableExpressions.join(", ")})`;
2181
+ }
2152
2182
  /**
2153
2183
  * Build a string match predicate for an XQuery string.
2154
2184
  */
@@ -2174,8 +2204,7 @@ function buildCombinedRawStringMatchPredicate(params) {
2174
2204
  matchMode,
2175
2205
  isCaseSensitive
2176
2206
  }));
2177
- if (predicates.length === 1) return predicates[0] ?? "false()";
2178
- return `(${predicates.join(" or ")})`;
2207
+ return buildOrPredicate(predicates);
2179
2208
  }
2180
2209
  /**
2181
2210
  * Build CTS word-query options for API v2 includes search.
@@ -2216,44 +2245,52 @@ function buildTokenizedSearchDeclarations(params) {
2216
2245
  termsVar
2217
2246
  };
2218
2247
  }
2219
- /**
2220
- * Build a CTS-backed field includes predicate for an XQuery string.
2221
- */
2222
- function buildCtsFieldIncludesPredicate(params) {
2223
- const { contentNodesExpression, valueExpression, value, isCaseSensitive, queryKey } = params;
2248
+ function buildCtsIncludesPredicate(params) {
2249
+ const { searchableNodesExpression, fallbackValueExpression, value, isCaseSensitive, queryKey, buildCandidateTermQuery } = params;
2224
2250
  const tokenizedSearchDeclarations = buildTokenizedSearchDeclarations({
2225
2251
  value,
2226
2252
  isCaseSensitive,
2227
2253
  queryKey
2228
2254
  });
2229
- const ctsQueryVar = `$query${queryKey}CtsQuery`;
2230
- const contentNodeVar = `$query${queryKey}ContentNode`;
2255
+ const termQueriesVar = `$query${queryKey}TermQueries`;
2256
+ const candidateQueryVar = `$query${queryKey}CandidateQuery`;
2231
2257
  const fallbackPredicate = buildRawStringMatchPredicate({
2232
- valueExpression,
2258
+ valueExpression: fallbackValueExpression,
2233
2259
  value,
2234
2260
  matchMode: "includes",
2235
2261
  isCaseSensitive
2236
2262
  });
2263
+ if (tokenizeIncludesSearchValue({
2264
+ value,
2265
+ isCaseSensitive
2266
+ }).length === 0) return {
2267
+ declarations: [],
2268
+ predicate: fallbackPredicate,
2269
+ candidateQueryVar: null
2270
+ };
2237
2271
  return {
2238
- declarations: [...tokenizedSearchDeclarations.declarations, `let ${ctsQueryVar} :=
2272
+ declarations: [
2273
+ ...tokenizedSearchDeclarations.declarations,
2274
+ `let ${termQueriesVar} :=
2275
+ for $term in ${tokenizedSearchDeclarations.termsVar}
2276
+ return ${buildCandidateTermQuery("$term")}`,
2277
+ `let ${candidateQueryVar} :=
2239
2278
  if (count(${tokenizedSearchDeclarations.termsVar}) = 1)
2240
- then ${buildCtsWordQueryExpression({
2241
- termExpression: `${tokenizedSearchDeclarations.termsVar}[1]`,
2242
- isCaseSensitive
2243
- })}
2279
+ then ${termQueriesVar}[1]
2244
2280
  else if (count(${tokenizedSearchDeclarations.termsVar}) gt 1)
2245
- then cts:and-query((
2246
- for $term in ${tokenizedSearchDeclarations.termsVar}
2247
- return ${buildCtsWordQueryExpression({
2281
+ then cts:and-query(${termQueriesVar})
2282
+ else ()`
2283
+ ],
2284
+ predicate: `(every $term in ${tokenizedSearchDeclarations.termsVar}
2285
+ satisfies some $searchNode in (${searchableNodesExpression})
2286
+ satisfies cts:contains(
2287
+ $searchNode,
2288
+ ${buildCtsWordQueryExpression({
2248
2289
  termExpression: "$term",
2249
2290
  isCaseSensitive
2250
2291
  })}
2251
- ))
2252
- else ()`],
2253
- predicate: `(if (exists(${ctsQueryVar}))
2254
- then some ${contentNodeVar} in (${contentNodesExpression})
2255
- satisfies cts:contains(${contentNodeVar}, ${ctsQueryVar})
2256
- else ${fallbackPredicate})`
2292
+ ))`,
2293
+ candidateQueryVar
2257
2294
  };
2258
2295
  }
2259
2296
  /**
@@ -2262,6 +2299,9 @@ function buildCtsFieldIncludesPredicate(params) {
2262
2299
  function buildItemStringSearchPaths(language) {
2263
2300
  return [buildFlattenedContentValuesExpression(`identification/label/content[@xml:lang="${language}"]`), buildFlattenedContentValuesExpression(`properties//property/value[not(@inherited="true")]/content[@xml:lang="${language}"]`)];
2264
2301
  }
2302
+ function buildItemStringSearchableNodesExpression(language) {
2303
+ return buildCombinedSearchableContentNodesExpression([`identification/label/content[@xml:lang="${language}"]`, `properties//property/value[not(@inherited="true")]/content[@xml:lang="${language}"]`]);
2304
+ }
2265
2305
  /**
2266
2306
  * Build the identification branch for an item-level CTS string search.
2267
2307
  */
@@ -2304,7 +2344,7 @@ function buildItemStringPropertyValueBranch(params) {
2304
2344
  /**
2305
2345
  * Build an item-level CTS string search predicate.
2306
2346
  */
2307
- function buildItemStringSearchPredicate(params) {
2347
+ function buildItemStringSearchClause(params) {
2308
2348
  const { query, version, queryKey } = params;
2309
2349
  const fallbackPredicate = buildCombinedRawStringMatchPredicate({
2310
2350
  valueExpressions: buildItemStringSearchPaths(query.language),
@@ -2314,55 +2354,292 @@ function buildItemStringSearchPredicate(params) {
2314
2354
  });
2315
2355
  if (query.matchMode !== "includes" || version !== 2) return {
2316
2356
  declarations: [],
2317
- predicate: fallbackPredicate
2357
+ predicate: fallbackPredicate,
2358
+ candidateQueryVar: null
2318
2359
  };
2319
- const tokenizedSearchDeclarations = buildTokenizedSearchDeclarations({
2360
+ return buildCtsIncludesPredicate({
2361
+ searchableNodesExpression: buildItemStringSearchableNodesExpression(query.language),
2362
+ fallbackValueExpression: `(${buildItemStringSearchPaths(query.language).join(", ")})`,
2320
2363
  value: query.value,
2321
2364
  isCaseSensitive: query.isCaseSensitive,
2365
+ queryKey,
2366
+ buildCandidateTermQuery: (termExpression) => `cts:or-query((
2367
+ ${buildItemStringIdentificationBranch({
2368
+ termExpression,
2369
+ isCaseSensitive: query.isCaseSensitive,
2370
+ language: query.language
2371
+ })},
2372
+ ${buildItemStringPropertyValueBranch({
2373
+ termExpression,
2374
+ isCaseSensitive: query.isCaseSensitive,
2375
+ language: query.language
2376
+ })}
2377
+ ))`
2378
+ });
2379
+ }
2380
+ function buildOrPredicate(predicates) {
2381
+ if (predicates.length === 0) return "false()";
2382
+ if (predicates.length === 1) return predicates[0] ?? "false()";
2383
+ return `(${predicates.join(" or ")})`;
2384
+ }
2385
+ function buildAndPredicate(predicates) {
2386
+ if (predicates.length === 0) return "true()";
2387
+ if (predicates.length === 1) return predicates[0] ?? "true()";
2388
+ return `(${predicates.join(" and ")})`;
2389
+ }
2390
+ /**
2391
+ * Build a date/dateTime range predicate for an XQuery string.
2392
+ */
2393
+ function buildDateRangePredicate(params) {
2394
+ const { from, to } = params;
2395
+ const conditions = [];
2396
+ if (from != null) conditions.push(`(value/@rawValue ge ${stringLiteral(from)})`);
2397
+ if (to != null) conditions.push(`(value/@rawValue le ${stringLiteral(to)})`);
2398
+ return buildAndPredicate(conditions);
2399
+ }
2400
+ /**
2401
+ * Build a property label predicate for an XQuery string.
2402
+ */
2403
+ function buildPropertyLabelPredicate(propertyVariables) {
2404
+ const labelPredicates = [];
2405
+ for (const propertyVariable of propertyVariables) labelPredicates.push(`@uuid=${stringLiteral(propertyVariable)}`);
2406
+ return `label[${buildOrPredicate(labelPredicates)}]`;
2407
+ }
2408
+ function buildOrCtsQueryExpression(queries) {
2409
+ if (queries.length === 1) return queries[0] ?? "cts:false-query()";
2410
+ return `cts:or-query((${queries.join(", ")}))`;
2411
+ }
2412
+ function buildContentTargetCandidateBranch(params) {
2413
+ const { containerElementName, termExpression, isCaseSensitive, language } = params;
2414
+ return `cts:element-query(xs:QName("${containerElementName}"),
2415
+ cts:and-query((
2416
+ cts:element-attribute-value-query(xs:QName("content"), xs:QName("xml:lang"), ${stringLiteral(language)}),
2417
+ ${buildCtsWordQueryExpression({
2418
+ termExpression,
2419
+ isCaseSensitive
2420
+ })}
2421
+ ))
2422
+ )`;
2423
+ }
2424
+ function buildPropertyStringCandidateBranch(params) {
2425
+ const { termExpression, isCaseSensitive, language } = params;
2426
+ return `cts:element-query(xs:QName("properties"),
2427
+ cts:element-query(xs:QName("property"),
2428
+ cts:element-query(xs:QName("value"),
2429
+ cts:and-query((
2430
+ cts:not-query(cts:element-attribute-value-query(xs:QName("value"), xs:QName("inherited"), "true")),
2431
+ cts:element-query(xs:QName("content"),
2432
+ cts:and-query((
2433
+ cts:element-attribute-value-query(xs:QName("content"), xs:QName("xml:lang"), ${stringLiteral(language)}),
2434
+ ${buildCtsWordQueryExpression({
2435
+ termExpression,
2436
+ isCaseSensitive
2437
+ })}
2438
+ ))
2439
+ )
2440
+ ))
2441
+ )
2442
+ )
2443
+ )`;
2444
+ }
2445
+ function getGroupableIncludesValue(query) {
2446
+ switch (query.target) {
2447
+ case "string":
2448
+ case "title":
2449
+ case "description":
2450
+ case "image":
2451
+ case "periods":
2452
+ case "bibliography": return query.value;
2453
+ case "property":
2454
+ if (query.dataType !== "string" || query.propertyValues?.length !== 1) return null;
2455
+ return query.propertyValues[0] ?? null;
2456
+ }
2457
+ }
2458
+ function isCompatibleIncludesGroupQuery(params) {
2459
+ const { query, value, isCaseSensitive, language, version } = params;
2460
+ if (version !== 2 || query.matchMode !== "includes" || query.isNegated === true) return false;
2461
+ const queryValue = getGroupableIncludesValue(query);
2462
+ return queryValue != null && queryValue === value && query.isCaseSensitive === isCaseSensitive && query.language === language;
2463
+ }
2464
+ function buildContentTargetIncludesGroupMember(params) {
2465
+ const { contentNodesExpression, value, isCaseSensitive, language, containerElementName } = params;
2466
+ return {
2467
+ rawPredicate: buildRawStringMatchPredicate({
2468
+ valueExpression: buildFlattenedContentValuesExpression(contentNodesExpression),
2469
+ value,
2470
+ matchMode: "includes",
2471
+ isCaseSensitive
2472
+ }),
2473
+ buildCandidateTermQuery: (termExpression) => buildContentTargetCandidateBranch({
2474
+ containerElementName,
2475
+ termExpression,
2476
+ isCaseSensitive,
2477
+ language
2478
+ })
2479
+ };
2480
+ }
2481
+ function buildItemStringIncludesGroupMember(query) {
2482
+ return {
2483
+ rawPredicate: buildCombinedRawStringMatchPredicate({
2484
+ valueExpressions: buildItemStringSearchPaths(query.language),
2485
+ value: query.value,
2486
+ matchMode: "includes",
2487
+ isCaseSensitive: query.isCaseSensitive
2488
+ }),
2489
+ buildCandidateTermQuery: (termExpression) => `cts:or-query((
2490
+ ${buildItemStringIdentificationBranch({
2491
+ termExpression,
2492
+ isCaseSensitive: query.isCaseSensitive,
2493
+ language: query.language
2494
+ })},
2495
+ ${buildItemStringPropertyValueBranch({
2496
+ termExpression,
2497
+ isCaseSensitive: query.isCaseSensitive,
2498
+ language: query.language
2499
+ })}
2500
+ ))`
2501
+ };
2502
+ }
2503
+ function buildPropertyStringIncludesGroupMember(query) {
2504
+ const propertyVariables = query.propertyVariables ?? [];
2505
+ const predicateParts = [];
2506
+ const valueExpression = buildFlattenedContentValuesExpression(`value[not(@inherited="true")]/content[@xml:lang="${query.language}"]`);
2507
+ const propertyValue = query.propertyValues[0] ?? "";
2508
+ if (propertyVariables.length > 0) predicateParts.push(buildPropertyLabelPredicate(propertyVariables));
2509
+ return {
2510
+ rawPredicate: `.//properties//property[${buildAndPredicate([...predicateParts, buildRawStringMatchPredicate({
2511
+ valueExpression,
2512
+ value: propertyValue,
2513
+ matchMode: "includes",
2514
+ isCaseSensitive: query.isCaseSensitive
2515
+ })])}]`,
2516
+ buildCandidateTermQuery: (termExpression) => buildPropertyStringCandidateBranch({
2517
+ termExpression,
2518
+ isCaseSensitive: query.isCaseSensitive,
2519
+ language: query.language
2520
+ })
2521
+ };
2522
+ }
2523
+ function buildIncludesGroupMember(query) {
2524
+ switch (query.target) {
2525
+ case "string": return buildItemStringIncludesGroupMember(query);
2526
+ case "title": return buildContentTargetIncludesGroupMember({
2527
+ contentNodesExpression: `identification/label/content[@xml:lang="${query.language}"]`,
2528
+ value: query.value,
2529
+ isCaseSensitive: query.isCaseSensitive,
2530
+ language: query.language,
2531
+ containerElementName: "identification"
2532
+ });
2533
+ case "description": return buildContentTargetIncludesGroupMember({
2534
+ contentNodesExpression: `description/content[@xml:lang="${query.language}"]`,
2535
+ value: query.value,
2536
+ isCaseSensitive: query.isCaseSensitive,
2537
+ language: query.language,
2538
+ containerElementName: "description"
2539
+ });
2540
+ case "periods": return buildContentTargetIncludesGroupMember({
2541
+ contentNodesExpression: `periods/period/identification/label/content[@xml:lang="${query.language}"]`,
2542
+ value: query.value,
2543
+ isCaseSensitive: query.isCaseSensitive,
2544
+ language: query.language,
2545
+ containerElementName: "period"
2546
+ });
2547
+ case "bibliography": return buildContentTargetIncludesGroupMember({
2548
+ contentNodesExpression: `bibliographies/bibliography/identification/label/content[@xml:lang="${query.language}"]`,
2549
+ value: query.value,
2550
+ isCaseSensitive: query.isCaseSensitive,
2551
+ language: query.language,
2552
+ containerElementName: "bibliography"
2553
+ });
2554
+ case "image": return buildContentTargetIncludesGroupMember({
2555
+ contentNodesExpression: `image/identification/label/content[@xml:lang="${query.language}"]`,
2556
+ value: query.value,
2557
+ isCaseSensitive: query.isCaseSensitive,
2558
+ language: query.language,
2559
+ containerElementName: "image"
2560
+ });
2561
+ case "property": return buildPropertyStringIncludesGroupMember(query);
2562
+ }
2563
+ }
2564
+ function findCompatibleIncludesGroup(params) {
2565
+ const { queries, startIndex, version } = params;
2566
+ const startQuery = queries[startIndex];
2567
+ if (startQuery == null) return null;
2568
+ const groupValue = getGroupableIncludesValue(startQuery);
2569
+ if (groupValue == null || !isCompatibleIncludesGroupQuery({
2570
+ query: startQuery,
2571
+ value: groupValue,
2572
+ isCaseSensitive: startQuery.isCaseSensitive,
2573
+ language: startQuery.language,
2574
+ version
2575
+ }) || startIndex > 0 && queries[startIndex]?.operator === "AND") return null;
2576
+ const groupedQueries = [startQuery];
2577
+ for (let index = startIndex + 1; index < queries.length; index += 1) {
2578
+ const query = queries[index];
2579
+ if (query?.operator !== "OR" || !isCompatibleIncludesGroupQuery({
2580
+ query,
2581
+ value: groupValue,
2582
+ isCaseSensitive: startQuery.isCaseSensitive,
2583
+ language: startQuery.language,
2584
+ version
2585
+ })) break;
2586
+ groupedQueries.push(query);
2587
+ }
2588
+ return groupedQueries.length > 1 ? groupedQueries : null;
2589
+ }
2590
+ function buildIncludesGroupClause(params) {
2591
+ const { queries, queryKey } = params;
2592
+ const firstQuery = queries[0];
2593
+ if (firstQuery == null) throw new Error("Cannot build an includes group without queries");
2594
+ const groupValue = getGroupableIncludesValue(firstQuery);
2595
+ if (groupValue == null) throw new Error("Cannot build an includes group without a search value");
2596
+ const members = queries.map((query) => buildIncludesGroupMember(query));
2597
+ const tokenizedSearchDeclarations = buildTokenizedSearchDeclarations({
2598
+ value: groupValue,
2599
+ isCaseSensitive: firstQuery.isCaseSensitive,
2322
2600
  queryKey
2323
2601
  });
2602
+ const tokenizedTerms = tokenizeIncludesSearchValue({
2603
+ value: groupValue,
2604
+ isCaseSensitive: firstQuery.isCaseSensitive
2605
+ });
2324
2606
  const termQueriesVar = `$query${queryKey}TermQueries`;
2325
- const ctsQueryVar = `$query${queryKey}CtsQuery`;
2607
+ const candidateQueryVar = `$query${queryKey}CandidateQuery`;
2608
+ if (tokenizedTerms.length === 0) return {
2609
+ declarations: [],
2610
+ predicate: buildOrPredicate(members.map((member) => member.rawPredicate)),
2611
+ candidateQueryVar: null
2612
+ };
2326
2613
  return {
2327
2614
  declarations: [
2328
2615
  ...tokenizedSearchDeclarations.declarations,
2329
2616
  `let ${termQueriesVar} :=
2330
2617
  for $term in ${tokenizedSearchDeclarations.termsVar}
2331
- return
2332
- cts:or-query((
2333
- ${buildItemStringIdentificationBranch({
2334
- termExpression: "$term",
2335
- isCaseSensitive: query.isCaseSensitive,
2336
- language: query.language
2337
- })},
2338
- ${buildItemStringPropertyValueBranch({
2339
- termExpression: "$term",
2340
- isCaseSensitive: query.isCaseSensitive,
2341
- language: query.language
2342
- })}
2343
- ))`,
2344
- `let ${ctsQueryVar} :=
2618
+ return ${buildOrCtsQueryExpression(members.map((member) => member.buildCandidateTermQuery("$term")))}`,
2619
+ `let ${candidateQueryVar} :=
2345
2620
  if (count(${tokenizedSearchDeclarations.termsVar}) = 1)
2346
2621
  then ${termQueriesVar}[1]
2347
2622
  else if (count(${tokenizedSearchDeclarations.termsVar}) gt 1)
2348
2623
  then cts:and-query(${termQueriesVar})
2349
2624
  else ()`
2350
2625
  ],
2351
- predicate: `(if (exists(${ctsQueryVar})) then cts:contains(., ${ctsQueryVar}) else ${fallbackPredicate})`
2626
+ predicate: `cts:contains(., ${candidateQueryVar})`,
2627
+ candidateQueryVar
2352
2628
  };
2353
2629
  }
2354
2630
  /**
2355
2631
  * Build a string match predicate for an XQuery string.
2356
2632
  */
2357
- function buildStringMatchPredicate(params) {
2358
- const { contentNodesExpression, value, matchMode, isCaseSensitive, version, queryKey } = params;
2633
+ function buildStringMatchClause(params) {
2634
+ const { contentNodesExpression, value, matchMode, isCaseSensitive, version, queryKey, buildCandidateTermQuery } = params;
2359
2635
  const valueExpression = buildFlattenedContentValuesExpression(contentNodesExpression);
2360
- if (matchMode === "includes" && version === 2) return buildCtsFieldIncludesPredicate({
2361
- contentNodesExpression,
2362
- valueExpression,
2636
+ if (matchMode === "includes" && version === 2 && buildCandidateTermQuery != null) return buildCtsIncludesPredicate({
2637
+ searchableNodesExpression: buildSearchableContentNodesExpression(contentNodesExpression),
2638
+ fallbackValueExpression: valueExpression,
2363
2639
  value,
2364
2640
  isCaseSensitive,
2365
- queryKey
2641
+ queryKey,
2642
+ buildCandidateTermQuery
2366
2643
  });
2367
2644
  return {
2368
2645
  declarations: [],
@@ -2371,70 +2648,67 @@ function buildStringMatchPredicate(params) {
2371
2648
  value,
2372
2649
  matchMode,
2373
2650
  isCaseSensitive
2374
- })
2651
+ }),
2652
+ candidateQueryVar: null
2375
2653
  };
2376
2654
  }
2377
- function buildOrPredicate(predicates) {
2378
- if (predicates.length === 1) return predicates[0] ?? "false()";
2379
- return `(${predicates.join(" or ")})`;
2380
- }
2381
- function buildAndPredicate(predicates) {
2382
- if (predicates.length === 1) return predicates[0] ?? "false()";
2383
- return `(${predicates.join(" and ")})`;
2384
- }
2385
- /**
2386
- * Build a date/dateTime range predicate for an XQuery string.
2387
- */
2388
- function buildDateRangePredicate(params) {
2389
- const { from, to } = params;
2390
- const conditions = [];
2391
- if (from != null) conditions.push(`(value/@rawValue ge ${stringLiteral(from)})`);
2392
- if (to != null) conditions.push(`(value/@rawValue le ${stringLiteral(to)})`);
2393
- return conditions.join(" and ");
2394
- }
2395
- /**
2396
- * Build a property label predicate for an XQuery string.
2397
- */
2398
- function buildPropertyLabelPredicate(propertyVariables) {
2399
- const labelPredicates = [];
2400
- for (const propertyVariable of propertyVariables) labelPredicates.push(`@uuid=${stringLiteral(propertyVariable)}`);
2401
- return `label[${buildOrPredicate(labelPredicates)}]`;
2402
- }
2403
2655
  function buildPropertyValueAttributePredicate(params) {
2404
2656
  const { propertyValues, attributeName } = params;
2405
2657
  const valuePredicates = [];
2406
2658
  for (const propertyValue of propertyValues) valuePredicates.push(`value[@${attributeName}=${stringLiteral(propertyValue)}]`);
2407
2659
  return buildOrPredicate(valuePredicates);
2408
2660
  }
2409
- function buildPropertyStringValuePredicate(params) {
2661
+ function buildPropertyStringValueClause(params) {
2410
2662
  const { query, version, queryKey } = params;
2411
2663
  const propertyContentNodesExpression = query.matchMode === "includes" && version === 2 ? `value[not(@inherited="true")]/content[@xml:lang="${query.language}"]` : `value/content[@xml:lang="${query.language}"]`;
2412
2664
  const declarations = [];
2413
2665
  const valuePredicates = [];
2666
+ const candidateQueryVars = [];
2414
2667
  for (const [propertyValueIndex, propertyValue] of query.propertyValues.entries()) {
2415
- const compiledStringPredicate = buildStringMatchPredicate({
2668
+ const compiledStringClause = buildStringMatchClause({
2416
2669
  contentNodesExpression: propertyContentNodesExpression,
2417
2670
  value: propertyValue,
2418
2671
  matchMode: query.matchMode,
2419
2672
  isCaseSensitive: query.isCaseSensitive,
2420
2673
  version,
2421
- queryKey: `${queryKey}_${propertyValueIndex + 1}`
2674
+ queryKey: `${queryKey}_${propertyValueIndex + 1}`,
2675
+ buildCandidateTermQuery: (termExpression) => buildPropertyStringCandidateBranch({
2676
+ termExpression,
2677
+ isCaseSensitive: query.isCaseSensitive,
2678
+ language: query.language
2679
+ })
2422
2680
  });
2423
- declarations.push(...compiledStringPredicate.declarations);
2424
- valuePredicates.push(compiledStringPredicate.predicate);
2681
+ declarations.push(...compiledStringClause.declarations);
2682
+ valuePredicates.push(compiledStringClause.predicate);
2683
+ if (compiledStringClause.candidateQueryVar != null) candidateQueryVars.push(compiledStringClause.candidateQueryVar);
2684
+ }
2685
+ let candidateQueryVar = null;
2686
+ if (candidateQueryVars.length > 0) {
2687
+ const candidateQueriesExpression = `(${candidateQueryVars.join(", ")})`;
2688
+ candidateQueryVar = `$query${queryKey}CandidateQuery`;
2689
+ declarations.push(`let ${candidateQueryVar} :=
2690
+ if (count(${candidateQueriesExpression}) = 1)
2691
+ then ${candidateQueriesExpression}[1]
2692
+ else if (count(${candidateQueriesExpression}) gt 1)
2693
+ then cts:or-query(${candidateQueriesExpression})
2694
+ else ()`);
2425
2695
  }
2426
2696
  return {
2427
2697
  declarations,
2428
- predicate: buildOrPredicate(valuePredicates)
2698
+ predicate: buildOrPredicate(valuePredicates),
2699
+ candidateQueryVar
2429
2700
  };
2430
2701
  }
2431
2702
  /**
2432
2703
  * Build a property predicate for an XQuery string.
2433
2704
  */
2434
- function buildPropertyPredicate(params) {
2705
+ function buildPropertyClause(params) {
2435
2706
  const { query, version, queryKey } = params;
2436
- const predicateParts = [buildPropertyLabelPredicate(query.propertyVariables)];
2707
+ const predicateParts = [];
2437
2708
  const declarations = [];
2709
+ const propertyVariables = query.propertyVariables ?? [];
2710
+ let candidateQueryVar = null;
2711
+ if (propertyVariables.length > 0) predicateParts.push(buildPropertyLabelPredicate(propertyVariables));
2438
2712
  if (query.dataType === "date" || query.dataType === "dateTime") predicateParts.push(buildDateRangePredicate({
2439
2713
  from: query.from,
2440
2714
  to: query.to
@@ -2456,73 +2730,105 @@ function buildPropertyPredicate(params) {
2456
2730
  }));
2457
2731
  break;
2458
2732
  case "string": {
2459
- const compiledStringPredicate = buildPropertyStringValuePredicate({
2733
+ const compiledStringClause = buildPropertyStringValueClause({
2460
2734
  query,
2461
2735
  version,
2462
2736
  queryKey
2463
2737
  });
2464
- declarations.push(...compiledStringPredicate.declarations);
2465
- predicateParts.push(compiledStringPredicate.predicate);
2738
+ declarations.push(...compiledStringClause.declarations);
2739
+ predicateParts.push(compiledStringClause.predicate);
2740
+ candidateQueryVar = compiledStringClause.candidateQueryVar;
2466
2741
  break;
2467
2742
  }
2468
2743
  }
2469
2744
  return {
2470
2745
  declarations,
2471
- predicate: `.//properties//property[${buildAndPredicate(predicateParts)}]`
2746
+ predicate: `.//properties//property[${buildAndPredicate(predicateParts)}]`,
2747
+ candidateQueryVar
2472
2748
  };
2473
2749
  }
2474
2750
  /**
2475
2751
  * Build a query predicate for an XQuery string.
2476
2752
  */
2477
- function buildQueryPredicate(params) {
2753
+ function buildQueryClause(params) {
2478
2754
  const { query, version, queryKey } = params;
2479
2755
  switch (query.target) {
2480
- case "string": return buildItemStringSearchPredicate({
2756
+ case "string": return buildItemStringSearchClause({
2481
2757
  query,
2482
2758
  version,
2483
2759
  queryKey
2484
2760
  });
2485
- case "title": return buildStringMatchPredicate({
2761
+ case "title": return buildStringMatchClause({
2486
2762
  contentNodesExpression: `identification/label/content[@xml:lang="${query.language}"]`,
2487
2763
  value: query.value,
2488
2764
  matchMode: query.matchMode,
2489
2765
  isCaseSensitive: query.isCaseSensitive,
2490
2766
  version,
2491
- queryKey
2767
+ queryKey,
2768
+ buildCandidateTermQuery: (termExpression) => buildContentTargetCandidateBranch({
2769
+ containerElementName: "identification",
2770
+ termExpression,
2771
+ isCaseSensitive: query.isCaseSensitive,
2772
+ language: query.language
2773
+ })
2492
2774
  });
2493
- case "description": return buildStringMatchPredicate({
2775
+ case "description": return buildStringMatchClause({
2494
2776
  contentNodesExpression: `description/content[@xml:lang="${query.language}"]`,
2495
2777
  value: query.value,
2496
2778
  matchMode: query.matchMode,
2497
2779
  isCaseSensitive: query.isCaseSensitive,
2498
2780
  version,
2499
- queryKey
2781
+ queryKey,
2782
+ buildCandidateTermQuery: (termExpression) => buildContentTargetCandidateBranch({
2783
+ containerElementName: "description",
2784
+ termExpression,
2785
+ isCaseSensitive: query.isCaseSensitive,
2786
+ language: query.language
2787
+ })
2500
2788
  });
2501
- case "periods": return buildStringMatchPredicate({
2789
+ case "periods": return buildStringMatchClause({
2502
2790
  contentNodesExpression: `periods/period/identification/label/content[@xml:lang="${query.language}"]`,
2503
2791
  value: query.value,
2504
2792
  matchMode: query.matchMode,
2505
2793
  isCaseSensitive: query.isCaseSensitive,
2506
2794
  version,
2507
- queryKey
2795
+ queryKey,
2796
+ buildCandidateTermQuery: (termExpression) => buildContentTargetCandidateBranch({
2797
+ containerElementName: "period",
2798
+ termExpression,
2799
+ isCaseSensitive: query.isCaseSensitive,
2800
+ language: query.language
2801
+ })
2508
2802
  });
2509
- case "bibliography": return buildStringMatchPredicate({
2803
+ case "bibliography": return buildStringMatchClause({
2510
2804
  contentNodesExpression: `bibliographies/bibliography/identification/label/content[@xml:lang="${query.language}"]`,
2511
2805
  value: query.value,
2512
2806
  matchMode: query.matchMode,
2513
2807
  isCaseSensitive: query.isCaseSensitive,
2514
2808
  version,
2515
- queryKey
2809
+ queryKey,
2810
+ buildCandidateTermQuery: (termExpression) => buildContentTargetCandidateBranch({
2811
+ containerElementName: "bibliography",
2812
+ termExpression,
2813
+ isCaseSensitive: query.isCaseSensitive,
2814
+ language: query.language
2815
+ })
2516
2816
  });
2517
- case "image": return buildStringMatchPredicate({
2817
+ case "image": return buildStringMatchClause({
2518
2818
  contentNodesExpression: `image/identification/label/content[@xml:lang="${query.language}"]`,
2519
2819
  value: query.value,
2520
2820
  matchMode: query.matchMode,
2521
2821
  isCaseSensitive: query.isCaseSensitive,
2522
2822
  version,
2523
- queryKey
2823
+ queryKey,
2824
+ buildCandidateTermQuery: (termExpression) => buildContentTargetCandidateBranch({
2825
+ containerElementName: "image",
2826
+ termExpression,
2827
+ isCaseSensitive: query.isCaseSensitive,
2828
+ language: query.language
2829
+ })
2524
2830
  });
2525
- case "property": return buildPropertyPredicate({
2831
+ case "property": return buildPropertyClause({
2526
2832
  query,
2527
2833
  version,
2528
2834
  queryKey
@@ -2534,27 +2840,36 @@ function buildQueryPredicate(params) {
2534
2840
  */
2535
2841
  function buildBooleanQueryClause(params) {
2536
2842
  const { query, version, queryKey } = params;
2537
- const compiledQueryPredicate = buildQueryPredicate({
2843
+ const compiledQueryClause = buildQueryClause({
2538
2844
  query,
2539
2845
  version,
2540
2846
  queryKey
2541
2847
  });
2542
- const baseClause = `(${compiledQueryPredicate.predicate})`;
2848
+ const baseClause = `(${compiledQueryClause.predicate})`;
2543
2849
  return {
2544
- declarations: compiledQueryPredicate.declarations,
2545
- predicate: query.isNegated ? `not(${baseClause})` : baseClause
2850
+ declarations: compiledQueryClause.declarations,
2851
+ predicate: query.isNegated ? `not(${baseClause})` : baseClause,
2852
+ candidateQueryVar: query.isNegated ? null : compiledQueryClause.candidateQueryVar
2546
2853
  };
2547
2854
  }
2548
- /**
2549
- * Build query filters for an XQuery string.
2550
- */
2551
- function buildQueryFilters(params) {
2552
- const { queries, version } = params;
2855
+ function buildQueryPlan(params) {
2856
+ const { queries, version, baseItemsExpression } = params;
2553
2857
  const declarations = [];
2554
2858
  const predicateParts = [];
2859
+ const candidateQueryVars = [];
2555
2860
  let hasCtsIncludesClauses = false;
2556
- for (const [index, query] of queries.entries()) {
2557
- const compiledClause = buildBooleanQueryClause({
2861
+ for (let index = 0; index < queries.length;) {
2862
+ const groupedQueries = findCompatibleIncludesGroup({
2863
+ queries,
2864
+ startIndex: index,
2865
+ version
2866
+ });
2867
+ const query = queries[index];
2868
+ if (query == null) break;
2869
+ const compiledClause = groupedQueries != null ? buildIncludesGroupClause({
2870
+ queries: groupedQueries,
2871
+ queryKey: `${index + 1}`
2872
+ }) : buildBooleanQueryClause({
2558
2873
  query,
2559
2874
  version,
2560
2875
  queryKey: `${index + 1}`
@@ -2563,15 +2878,33 @@ function buildQueryFilters(params) {
2563
2878
  hasCtsIncludesClauses = true;
2564
2879
  declarations.push(...compiledClause.declarations);
2565
2880
  }
2881
+ if (compiledClause.candidateQueryVar != null) candidateQueryVars.push(compiledClause.candidateQueryVar);
2566
2882
  if (index === 0) {
2567
2883
  predicateParts.push(compiledClause.predicate);
2884
+ index += groupedQueries?.length ?? 1;
2568
2885
  continue;
2569
2886
  }
2570
2887
  predicateParts.push(`${query.operator === "AND" ? "and" : "or"} ${compiledClause.predicate}`);
2888
+ index += groupedQueries?.length ?? 1;
2571
2889
  }
2572
2890
  if (hasCtsIncludesClauses) declarations.unshift(`let ${CTS_INCLUDES_STOP_WORDS_VAR} := (${CTS_INCLUDES_STOP_WORDS.map((stopWord) => stringLiteral(stopWord)).join(", ")})`);
2891
+ let itemsExpression = `(${baseItemsExpression})`;
2892
+ if (candidateQueryVars.length > 0) {
2893
+ const candidateQueriesExpression = `(${candidateQueryVars.join(", ")})`;
2894
+ const candidateItemsQueryVar = "$candidateItemsQuery";
2895
+ declarations.push(`let ${candidateItemsQueryVar} :=
2896
+ if (count(${candidateQueriesExpression}) = 1)
2897
+ then ${candidateQueriesExpression}[1]
2898
+ else if (count(${candidateQueriesExpression}) gt 1)
2899
+ then cts:or-query(${candidateQueriesExpression})
2900
+ else ()`);
2901
+ itemsExpression = `(if (exists(${candidateItemsQueryVar}))
2902
+ then cts:search(${baseItemsExpression}, ${candidateItemsQueryVar})
2903
+ else ${baseItemsExpression})`;
2904
+ }
2573
2905
  return {
2574
2906
  declarations,
2907
+ itemsExpression,
2575
2908
  predicate: predicateParts.join(" ")
2576
2909
  };
2577
2910
  }
@@ -2667,22 +3000,21 @@ function buildXQuery$1(params, options) {
2667
3000
  const startPosition = (page - 1) * pageSize + 1;
2668
3001
  const endPosition = page * pageSize;
2669
3002
  const setScopeFilter = `/set[(${setScopeUuids.map((uuid) => `@uuid="${uuid}"`).join(" or ")})]/items/*`;
2670
- const compiledQueryFilters = buildQueryFilters({
3003
+ const compiledQueryPlan = buildQueryPlan({
2671
3004
  queries,
2672
- version
3005
+ version,
3006
+ baseItemsExpression: `${version === 2 ? "doc()" : "input()"}/ochre${setScopeFilter}`
2673
3007
  });
2674
- const queryFilterDeclarations = compiledQueryFilters.declarations.length > 0 ? `${compiledQueryFilters.declarations.join("\n")}\n\n` : "";
3008
+ const queryFilterDeclarations = compiledQueryPlan.declarations.length > 0 ? `${compiledQueryPlan.declarations.join("\n")}\n\n` : "";
2675
3009
  const filterPredicates = [];
2676
3010
  if (belongsToCollectionScopeUuids.length > 0) {
2677
3011
  const belongsToCollectionScopeValues = belongsToCollectionScopeUuids.map((uuid) => `@uuid="${uuid}"`).join(" or ");
2678
3012
  filterPredicates.push(`.//properties[property[label/@uuid="${BELONGS_TO_COLLECTION_UUID}" and value/(${belongsToCollectionScopeValues})]]`);
2679
3013
  }
2680
- if (compiledQueryFilters.predicate.length > 0) filterPredicates.push(`(${compiledQueryFilters.predicate})`);
3014
+ if (compiledQueryPlan.predicate.length > 0) filterPredicates.push(`(${compiledQueryPlan.predicate})`);
2681
3015
  const itemFilters = filterPredicates.length > 0 ? `[${filterPredicates.join(" and ")}]` : "";
2682
3016
  const orderedItemsClause = buildOrderedItemsClause(sort);
2683
- return `<ochre>{${`${queryFilterDeclarations}let $items := ${version === 2 ? "doc()" : "input()"}/ochre
2684
- ${setScopeFilter}
2685
- ${itemFilters}
3017
+ return `<ochre>{${`${queryFilterDeclarations}let $items := ${compiledQueryPlan.itemsExpression}${itemFilters}
2686
3018
 
2687
3019
  let $totalCount := count($items)
2688
3020
  ${orderedItemsClause}
@@ -2807,7 +3139,7 @@ async function fetchSetItems(params, itemCategories, options) {
2807
3139
  }
2808
3140
  }
2809
3141
  //#endregion
2810
- //#region src/utils/fetchers/set/property-values-by-property-variables.ts
3142
+ //#region src/utils/fetchers/set/property-values.ts
2811
3143
  function parsePropertyValueLabel(content) {
2812
3144
  if (content == null || content === "") return null;
2813
3145
  if (typeof content === "object") return parseStringContent({ content });
@@ -2883,7 +3215,7 @@ function getPropertyVariableUuidsFromQueries(queries) {
2883
3215
  const propertyVariableUuids = /* @__PURE__ */ new Set();
2884
3216
  for (const query of queries) {
2885
3217
  if (query.target !== "property") continue;
2886
- for (const propertyVariableUuid of query.propertyVariables) propertyVariableUuids.add(propertyVariableUuid);
3218
+ for (const propertyVariableUuid of query.propertyVariables ?? []) propertyVariableUuids.add(propertyVariableUuid);
2887
3219
  }
2888
3220
  return [...propertyVariableUuids];
2889
3221
  }
@@ -2944,14 +3276,14 @@ const attributeValueQueryItemSchema = z.object({
2944
3276
  content: val.content != null && val.content !== "" ? val.content : null
2945
3277
  }));
2946
3278
  /**
2947
- * Schema for the property values by property variables OCHRE API response
3279
+ * Schema for the property values OCHRE API response
2948
3280
  */
2949
3281
  const responseSchema = z.object({ result: z.union([z.object({ ochre: z.object({
2950
3282
  propertyValue: z.union([propertyValueQueryItemSchema, z.array(propertyValueQueryItemSchema)]).optional(),
2951
3283
  attributeValue: z.union([attributeValueQueryItemSchema, z.array(attributeValueQueryItemSchema)]).optional()
2952
3284
  }) }), z.array(z.unknown()).length(0)]) });
2953
3285
  /**
2954
- * Build an XQuery string to fetch property values by property variables from the OCHRE API
3286
+ * Build an XQuery string to fetch property values from the OCHRE API
2955
3287
  * @param params - The parameters for the fetch
2956
3288
  * @param params.setScopeUuids - An array of set scope UUIDs to filter by
2957
3289
  * @param params.belongsToCollectionScopeUuids - An array of collection scope UUIDs to filter by
@@ -2970,17 +3302,18 @@ function buildXQuery(params, options) {
2970
3302
  let setScopeFilter = "/set/items/*";
2971
3303
  if (setScopeUuids.length > 0) setScopeFilter = `/set[(${setScopeUuids.map((uuid) => `@uuid="${uuid}"`).join(" or ")})]/items/*`;
2972
3304
  const propertyVariableFilters = getPropertyVariableUuidsFromQueries(queries).map((uuid) => `@uuid="${uuid}"`).join(" or ");
2973
- const compiledQueryFilters = buildQueryFilters({
3305
+ const compiledQueryPlan = buildQueryPlan({
2974
3306
  queries,
2975
- version
3307
+ version,
3308
+ baseItemsExpression: `${version === 2 ? "doc()" : "input()"}/ochre${setScopeFilter}`
2976
3309
  });
2977
- const queryFilterDeclarations = compiledQueryFilters.declarations.length > 0 ? `${compiledQueryFilters.declarations.join("\n")}\n\n` : "";
3310
+ const queryFilterDeclarations = compiledQueryPlan.declarations.length > 0 ? `${compiledQueryPlan.declarations.join("\n")}\n\n` : "";
2978
3311
  const filterPredicates = [];
2979
3312
  if (belongsToCollectionScopeUuids.length > 0) {
2980
3313
  const belongsToCollectionScopeValues = belongsToCollectionScopeUuids.map((uuid) => `@uuid="${uuid}"`).join(" or ");
2981
3314
  filterPredicates.push(`.//properties[property[label/@uuid="${BELONGS_TO_COLLECTION_UUID}" and value[${belongsToCollectionScopeValues}]]]`);
2982
3315
  }
2983
- if (compiledQueryFilters.predicate.length > 0) filterPredicates.push(`(${compiledQueryFilters.predicate})`);
3316
+ if (compiledQueryPlan.predicate.length > 0) filterPredicates.push(`(${compiledQueryPlan.predicate})`);
2984
3317
  const itemFilters = filterPredicates.length > 0 ? `[${filterPredicates.join(" and ")}]` : "";
2985
3318
  const queryBlocks = [`let $matching-props := $items//property[label[${propertyVariableFilters}]]
2986
3319
 
@@ -3011,16 +3344,14 @@ let $property-values :=
3011
3344
  return <attributeValue attributeType="periods" itemUuid="{$item/@uuid}" content="{$label}" />`);
3012
3345
  returnedSequences.push("$period-values");
3013
3346
  }
3014
- return `<ochre>{${`${queryFilterDeclarations}let $items := ${version === 2 ? "doc()" : "input()"}/ochre
3015
- ${setScopeFilter}
3016
- ${itemFilters}
3347
+ return `<ochre>{${`${queryFilterDeclarations}let $items := ${compiledQueryPlan.itemsExpression}${itemFilters}
3017
3348
 
3018
3349
  ${queryBlocks.join("\n\n")}
3019
3350
 
3020
3351
  return (${returnedSequences.join(", ")})`}}</ochre>`;
3021
3352
  }
3022
3353
  /**
3023
- * Fetches and parses Set property values by property variables from the OCHRE API
3354
+ * Fetches and parses Set property values from the OCHRE API
3024
3355
  *
3025
3356
  * @param params - The parameters for the fetch
3026
3357
  * @param params.setScopeUuids - An array of set scope UUIDs to filter by
@@ -3035,10 +3366,10 @@ return (${returnedSequences.join(", ")})`}}</ochre>`;
3035
3366
  * @returns Parsed Set property values and requested attribute values.
3036
3367
  * Returns empty arrays/objects when no matches are found, and null outputs on fetch/parse errors.
3037
3368
  */
3038
- async function fetchSetPropertyValuesByPropertyVariables(params, options) {
3369
+ async function fetchSetPropertyValues(params, options) {
3039
3370
  try {
3040
3371
  const version = options?.version ?? 2;
3041
- const { setScopeUuids, belongsToCollectionScopeUuids, queries, attributes, isLimitedToLeafPropertyValues } = setPropertyValuesByPropertyVariablesParamsSchema.parse(params);
3372
+ const { setScopeUuids, belongsToCollectionScopeUuids, queries, attributes, isLimitedToLeafPropertyValues } = setPropertyValuesParamsSchema.parse(params);
3042
3373
  const xquery = buildXQuery({
3043
3374
  setScopeUuids,
3044
3375
  belongsToCollectionScopeUuids,
@@ -3100,7 +3431,7 @@ async function fetchSetPropertyValuesByPropertyVariables(params, options) {
3100
3431
  propertyValues: null,
3101
3432
  propertyValuesByPropertyVariableUuid: null,
3102
3433
  attributeValues: null,
3103
- error: error instanceof Error ? error.message : "Failed to fetch property values by property variables"
3434
+ error: error instanceof Error ? error.message : "Failed to fetch property values"
3104
3435
  };
3105
3436
  }
3106
3437
  }
@@ -4700,4 +5031,4 @@ async function fetchWebsite(abbreviation, options) {
4700
5031
  }
4701
5032
  }
4702
5033
  //#endregion
4703
- export { DEFAULT_API_VERSION, DEFAULT_PAGE_SIZE, fetchGallery, fetchItem, fetchSetItems, fetchSetPropertyValuesByPropertyVariables, fetchWebsite, filterProperties, flattenItemProperties, getLeafPropertyValues, getPropertyByLabel, getPropertyByLabelAndValue, getPropertyByLabelAndValues, getPropertyByUuid, getPropertyValueByLabel, getPropertyValueByUuid, getPropertyValuesByLabel, getPropertyValuesByUuid, getUniqueProperties, getUniquePropertyLabels };
5034
+ export { DEFAULT_API_VERSION, DEFAULT_PAGE_SIZE, fetchGallery, fetchItem, fetchSetItems, fetchSetPropertyValues, fetchWebsite, filterProperties, flattenItemProperties, getLeafPropertyValues, getPropertyByLabel, getPropertyByLabelAndValue, getPropertyByLabelAndValues, getPropertyByUuid, getPropertyValueByLabel, getPropertyValueByUuid, getPropertyValuesByLabel, getPropertyValuesByUuid, getUniqueProperties, getUniquePropertyLabels };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ochre-sdk",
3
- "version": "0.20.24",
3
+ "version": "0.20.26",
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",