ochre-sdk 0.21.3 → 0.21.5

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
@@ -689,6 +689,19 @@ type QueryLeaf = {
689
689
  propertyVariable: string;
690
690
  dataType: "date" | "dateTime";
691
691
  propertyValues?: never;
692
+ value: string;
693
+ from?: never;
694
+ to?: never;
695
+ matchMode: "includes" | "exact";
696
+ isCaseSensitive: boolean;
697
+ language: string;
698
+ isNegated?: boolean;
699
+ } | {
700
+ target: "property";
701
+ propertyVariable: string;
702
+ dataType: "date" | "dateTime";
703
+ propertyValues?: never;
704
+ value?: never;
692
705
  from: string;
693
706
  to?: string;
694
707
  matchMode: "includes" | "exact";
@@ -700,6 +713,7 @@ type QueryLeaf = {
700
713
  propertyVariable: string;
701
714
  dataType: "date" | "dateTime";
702
715
  propertyValues?: never;
716
+ value?: never;
703
717
  from?: string;
704
718
  to: string;
705
719
  matchMode: "includes" | "exact";
@@ -793,9 +807,14 @@ type StylesheetItem = {
793
807
  mobile: Array<Style>;
794
808
  };
795
809
  };
796
- type WebsitePropertyQueryNode = Query extends infer QueryNode ? QueryNode extends {
810
+ type WebsitePropertyQueryNode = {
797
811
  target: "property";
798
- } ? Omit<QueryNode, "propertyValues" | "from" | "to" | "isNegated"> : never : never;
812
+ propertyVariable: string;
813
+ dataType: Exclude<PropertyValueContentType, "coordinate">;
814
+ matchMode: "includes" | "exact";
815
+ isCaseSensitive: boolean;
816
+ language: string;
817
+ };
799
818
  type WebsitePropertyQuery = WebsitePropertyQueryNode | {
800
819
  and: Array<WebsitePropertyQuery>;
801
820
  } | {
package/dist/index.mjs CHANGED
@@ -783,6 +783,19 @@ const setQueryLeafSchema = z.union([
783
783
  target: z.literal("property"),
784
784
  propertyVariable: uuidSchema,
785
785
  dataType: z.enum(["date", "dateTime"]),
786
+ value: z.string(),
787
+ from: z.never().optional(),
788
+ to: z.never().optional(),
789
+ matchMode: z.enum(["includes", "exact"]),
790
+ isCaseSensitive: z.boolean(),
791
+ language: z.string().default("eng"),
792
+ isNegated: z.boolean().optional().default(false)
793
+ }).strict(),
794
+ z.object({
795
+ target: z.literal("property"),
796
+ propertyVariable: uuidSchema,
797
+ dataType: z.enum(["date", "dateTime"]),
798
+ value: z.never().optional(),
786
799
  from: z.string(),
787
800
  to: z.string().optional(),
788
801
  matchMode: z.enum(["includes", "exact"]),
@@ -794,6 +807,7 @@ const setQueryLeafSchema = z.union([
794
807
  target: z.literal("property"),
795
808
  propertyVariable: uuidSchema,
796
809
  dataType: z.enum(["date", "dateTime"]),
810
+ value: z.never().optional(),
797
811
  from: z.string().optional(),
798
812
  to: z.string(),
799
813
  matchMode: z.enum(["includes", "exact"]),
@@ -2158,6 +2172,10 @@ function buildFlattenedContentValuesExpression(contentNodesExpression) {
2158
2172
  return `for $content in ${contentNodesExpression}
2159
2173
  return string-join($content//text(), "")`;
2160
2174
  }
2175
+ function buildNodeStringValuesExpression(nodesExpression) {
2176
+ return `for $node in ${nodesExpression}
2177
+ return string($node)`;
2178
+ }
2161
2179
  function buildSearchableContentNodesExpression(contentNodesExpression) {
2162
2180
  return `for $content in ${contentNodesExpression}
2163
2181
  return (
@@ -2172,6 +2190,18 @@ function buildCombinedSearchableContentNodesExpression(contentNodesExpressions)
2172
2190
  if (searchableExpressions.length === 1) return searchableExpressions[0] ?? "()";
2173
2191
  return `(${searchableExpressions.join(", ")})`;
2174
2192
  }
2193
+ function buildTokenizedSearchPredicate(params) {
2194
+ const { searchableNodesExpression, termsExpression, isCaseSensitive } = params;
2195
+ return `(every $term in ${termsExpression}
2196
+ satisfies some $searchNode in (${searchableNodesExpression})
2197
+ satisfies cts:contains(
2198
+ $searchNode,
2199
+ ${buildCtsWordQueryExpression({
2200
+ termExpression: "$term",
2201
+ isCaseSensitive
2202
+ })}
2203
+ ))`;
2204
+ }
2175
2205
  /**
2176
2206
  * Build a string match predicate for an XQuery string.
2177
2207
  */
@@ -2274,15 +2304,11 @@ function buildCtsIncludesPredicate(params) {
2274
2304
  then cts:and-query(${termQueriesVar})
2275
2305
  else ()`
2276
2306
  ],
2277
- predicate: `(every $term in ${tokenizedSearchDeclarations.termsVar}
2278
- satisfies some $searchNode in (${searchableNodesExpression})
2279
- satisfies cts:contains(
2280
- $searchNode,
2281
- ${buildCtsWordQueryExpression({
2282
- termExpression: "$term",
2307
+ predicate: buildTokenizedSearchPredicate({
2308
+ searchableNodesExpression,
2309
+ termsExpression: tokenizedSearchDeclarations.termsVar,
2283
2310
  isCaseSensitive
2284
- })}
2285
- ))`,
2311
+ }),
2286
2312
  candidateQueryVar
2287
2313
  };
2288
2314
  }
@@ -2442,6 +2468,75 @@ function buildPropertyStringCandidateBranch(params) {
2442
2468
  )
2443
2469
  )`;
2444
2470
  }
2471
+ function buildPropertyRawValueCandidateBranch(params) {
2472
+ const { termExpression, isCaseSensitive } = params;
2473
+ return `cts:element-query(xs:QName("properties"),
2474
+ cts:element-query(xs:QName("property"),
2475
+ cts:element-attribute-word-query(
2476
+ xs:QName("value"),
2477
+ xs:QName("rawValue"),
2478
+ ${termExpression},
2479
+ ${buildCtsQueryOptionsExpression(isCaseSensitive)}
2480
+ )
2481
+ )
2482
+ )`;
2483
+ }
2484
+ function buildPropertySimpleValueTextCandidateBranch(params) {
2485
+ const { termExpression, isCaseSensitive } = params;
2486
+ return `cts:element-query(xs:QName("properties"),
2487
+ cts:element-query(xs:QName("property"),
2488
+ cts:element-word-query(
2489
+ xs:QName("value"),
2490
+ ${termExpression},
2491
+ ${buildCtsQueryOptionsExpression(isCaseSensitive)}
2492
+ )
2493
+ )
2494
+ )`;
2495
+ }
2496
+ function buildPropertySimpleValueRawValueExpression() {
2497
+ return buildNodeStringValuesExpression("value/@rawValue");
2498
+ }
2499
+ function buildPropertySimpleValueTextExpression() {
2500
+ return buildNodeStringValuesExpression("value[not(*)]/text()");
2501
+ }
2502
+ function buildPropertySimpleValueSearchableNodesExpression() {
2503
+ return "value[not(*)]";
2504
+ }
2505
+ function buildPropertyRawValueOrTextRawPredicate(params) {
2506
+ const { value, matchMode, isCaseSensitive } = params;
2507
+ return buildOrPredicate([buildRawStringMatchPredicate({
2508
+ valueExpression: buildPropertySimpleValueRawValueExpression(),
2509
+ value,
2510
+ matchMode,
2511
+ isCaseSensitive
2512
+ }), buildRawStringMatchPredicate({
2513
+ valueExpression: buildPropertySimpleValueTextExpression(),
2514
+ value,
2515
+ matchMode,
2516
+ isCaseSensitive
2517
+ })]);
2518
+ }
2519
+ function buildPropertyRawValueOrTextTokenPredicate(params) {
2520
+ const { termsExpression, isCaseSensitive } = params;
2521
+ const rawValueQuery = `cts:element-attribute-word-query(
2522
+ xs:QName("value"),
2523
+ xs:QName("rawValue"),
2524
+ $term,
2525
+ ${buildCtsQueryOptionsExpression(isCaseSensitive)}
2526
+ )`;
2527
+ const textQuery = `cts:element-word-query(
2528
+ xs:QName("value"),
2529
+ $term,
2530
+ ${buildCtsQueryOptionsExpression(isCaseSensitive)}
2531
+ )`;
2532
+ return `(every $term in ${termsExpression}
2533
+ satisfies some $searchNode in (${buildPropertySimpleValueSearchableNodesExpression()})
2534
+ satisfies (
2535
+ cts:contains($searchNode, ${rawValueQuery})
2536
+ or
2537
+ cts:contains($searchNode, ${textQuery})
2538
+ ))`;
2539
+ }
2445
2540
  function getGroupableIncludesValue(query) {
2446
2541
  switch (query.target) {
2447
2542
  case "string":
@@ -2451,7 +2546,13 @@ function getGroupableIncludesValue(query) {
2451
2546
  case "periods":
2452
2547
  case "bibliography": return query.value;
2453
2548
  case "property":
2454
- if (query.dataType !== "string" || query.propertyValues?.length !== 1) return null;
2549
+ if (query.dataType === "string") {
2550
+ if (query.propertyValues?.length !== 1) return null;
2551
+ return query.propertyValues[0] ?? null;
2552
+ }
2553
+ if (query.dataType === "IDREF") return null;
2554
+ if (query.dataType === "date" || query.dataType === "dateTime") return "value" in query && query.value != null ? query.value : null;
2555
+ if (query.propertyValues?.length !== 1) return null;
2455
2556
  return query.propertyValues[0] ?? null;
2456
2557
  }
2457
2558
  }
@@ -2470,6 +2571,11 @@ function buildContentTargetIncludesGroupMember(params) {
2470
2571
  matchMode: "includes",
2471
2572
  isCaseSensitive
2472
2573
  }),
2574
+ buildTokenPredicate: (termsExpression) => buildTokenizedSearchPredicate({
2575
+ searchableNodesExpression: buildSearchableContentNodesExpression(contentNodesExpression),
2576
+ termsExpression,
2577
+ isCaseSensitive
2578
+ }),
2473
2579
  buildCandidateTermQuery: (termExpression) => buildContentTargetCandidateBranch({
2474
2580
  containerElementName,
2475
2581
  termExpression,
@@ -2486,6 +2592,11 @@ function buildItemStringIncludesGroupMember(query) {
2486
2592
  matchMode: "includes",
2487
2593
  isCaseSensitive: query.isCaseSensitive
2488
2594
  }),
2595
+ buildTokenPredicate: (termsExpression) => buildTokenizedSearchPredicate({
2596
+ searchableNodesExpression: buildItemStringSearchableNodesExpression(query.language),
2597
+ termsExpression,
2598
+ isCaseSensitive: query.isCaseSensitive
2599
+ }),
2489
2600
  buildCandidateTermQuery: (termExpression) => `cts:or-query((
2490
2601
  ${buildItemStringIdentificationBranch({
2491
2602
  termExpression,
@@ -2503,7 +2614,8 @@ function buildItemStringIncludesGroupMember(query) {
2503
2614
  function buildPropertyStringIncludesGroupMember(query) {
2504
2615
  const propertyVariable = query.propertyVariable;
2505
2616
  const predicateParts = [];
2506
- const valueExpression = buildFlattenedContentValuesExpression(`value[not(@inherited="true")]/content[@xml:lang="${query.language}"]`);
2617
+ const propertyContentNodesExpression = `value[not(@inherited="true")]/content[@xml:lang="${query.language}"]`;
2618
+ const valueExpression = buildFlattenedContentValuesExpression(propertyContentNodesExpression);
2507
2619
  const propertyValue = query.propertyValues[0] ?? "";
2508
2620
  if (propertyVariable != null) predicateParts.push(buildPropertyLabelPredicate(propertyVariable));
2509
2621
  return {
@@ -2513,6 +2625,11 @@ function buildPropertyStringIncludesGroupMember(query) {
2513
2625
  matchMode: "includes",
2514
2626
  isCaseSensitive: query.isCaseSensitive
2515
2627
  })]),
2628
+ buildTokenPredicate: (termsExpression) => buildPropertyPredicateExpression([...predicateParts, buildTokenizedSearchPredicate({
2629
+ searchableNodesExpression: buildSearchableContentNodesExpression(propertyContentNodesExpression),
2630
+ termsExpression,
2631
+ isCaseSensitive: query.isCaseSensitive
2632
+ })]),
2516
2633
  buildCandidateTermQuery: (termExpression) => buildPropertyStringCandidateBranch({
2517
2634
  termExpression,
2518
2635
  isCaseSensitive: query.isCaseSensitive,
@@ -2520,6 +2637,31 @@ function buildPropertyStringIncludesGroupMember(query) {
2520
2637
  })
2521
2638
  };
2522
2639
  }
2640
+ function buildPropertyRawValueOrTextIncludesGroupMember(query) {
2641
+ const propertyVariable = query.propertyVariable;
2642
+ const predicateParts = [];
2643
+ const propertyValue = getGroupableIncludesValue(query);
2644
+ if (propertyValue == null) throw new Error("Cannot build a rawValue/text includes group without a search value");
2645
+ if (propertyVariable != null) predicateParts.push(buildPropertyLabelPredicate(propertyVariable));
2646
+ return {
2647
+ rawPredicate: buildPropertyPredicateExpression([...predicateParts, buildPropertyRawValueOrTextRawPredicate({
2648
+ value: propertyValue,
2649
+ matchMode: "includes",
2650
+ isCaseSensitive: query.isCaseSensitive
2651
+ })]),
2652
+ buildTokenPredicate: (termsExpression) => buildPropertyPredicateExpression([...predicateParts, buildPropertyRawValueOrTextTokenPredicate({
2653
+ termsExpression,
2654
+ isCaseSensitive: query.isCaseSensitive
2655
+ })]),
2656
+ buildCandidateTermQuery: (termExpression) => buildOrCtsQueryExpression([buildPropertyRawValueCandidateBranch({
2657
+ termExpression,
2658
+ isCaseSensitive: query.isCaseSensitive
2659
+ }), buildPropertySimpleValueTextCandidateBranch({
2660
+ termExpression,
2661
+ isCaseSensitive: query.isCaseSensitive
2662
+ })])
2663
+ };
2664
+ }
2523
2665
  function buildIncludesGroupMember(query) {
2524
2666
  switch (query.target) {
2525
2667
  case "string": return buildItemStringIncludesGroupMember(query);
@@ -2558,7 +2700,9 @@ function buildIncludesGroupMember(query) {
2558
2700
  language: query.language,
2559
2701
  containerElementName: "image"
2560
2702
  });
2561
- case "property": return buildPropertyStringIncludesGroupMember(query);
2703
+ case "property":
2704
+ if (query.dataType === "string") return buildPropertyStringIncludesGroupMember(query);
2705
+ return buildPropertyRawValueOrTextIncludesGroupMember(query);
2562
2706
  }
2563
2707
  }
2564
2708
  function getCompatibleIncludesGroupLeaves(params) {
@@ -2624,7 +2768,7 @@ function buildIncludesGroupClause(params) {
2624
2768
  then cts:and-query(${termQueriesVar})
2625
2769
  else ()`
2626
2770
  ],
2627
- predicate: `cts:contains(., ${candidateQueryVar})`,
2771
+ predicate: buildOrPredicate(members.map((member) => member.buildTokenPredicate(tokenizedSearchDeclarations.termsVar))),
2628
2772
  candidateQueryVar
2629
2773
  };
2630
2774
  }
@@ -2659,6 +2803,94 @@ function buildPropertyValueAttributePredicate(params) {
2659
2803
  for (const propertyValue of propertyValues) valuePredicates.push(`value[@${attributeName}=${stringLiteral(propertyValue)}]`);
2660
2804
  return buildOrPredicate(valuePredicates);
2661
2805
  }
2806
+ function buildPropertyRawValueOrTextMatchClause(params) {
2807
+ const { value, matchMode, isCaseSensitive, version, queryKey } = params;
2808
+ const fallbackPredicate = buildPropertyRawValueOrTextRawPredicate({
2809
+ value,
2810
+ matchMode,
2811
+ isCaseSensitive
2812
+ });
2813
+ if (matchMode !== "includes" || version !== 2) return {
2814
+ declarations: [],
2815
+ predicate: fallbackPredicate,
2816
+ candidateQueryVar: null
2817
+ };
2818
+ const tokenizedSearchDeclarations = buildTokenizedSearchDeclarations({
2819
+ value,
2820
+ isCaseSensitive,
2821
+ queryKey
2822
+ });
2823
+ const tokenizedTerms = tokenizeIncludesSearchValue({
2824
+ value,
2825
+ isCaseSensitive
2826
+ });
2827
+ const termQueriesVar = `$query${queryKey}TermQueries`;
2828
+ const candidateQueryVar = `$query${queryKey}CandidateQuery`;
2829
+ if (tokenizedTerms.length === 0) return {
2830
+ declarations: [],
2831
+ predicate: fallbackPredicate,
2832
+ candidateQueryVar: null
2833
+ };
2834
+ return {
2835
+ declarations: [
2836
+ ...tokenizedSearchDeclarations.declarations,
2837
+ `let ${termQueriesVar} :=
2838
+ for $term in ${tokenizedSearchDeclarations.termsVar}
2839
+ return ${buildOrCtsQueryExpression([buildPropertyRawValueCandidateBranch({
2840
+ termExpression: "$term",
2841
+ isCaseSensitive
2842
+ }), buildPropertySimpleValueTextCandidateBranch({
2843
+ termExpression: "$term",
2844
+ isCaseSensitive
2845
+ })])}`,
2846
+ `let ${candidateQueryVar} :=
2847
+ if (count(${tokenizedSearchDeclarations.termsVar}) = 1)
2848
+ then ${termQueriesVar}[1]
2849
+ else if (count(${tokenizedSearchDeclarations.termsVar}) gt 1)
2850
+ then cts:and-query(${termQueriesVar})
2851
+ else ()`
2852
+ ],
2853
+ predicate: buildPropertyRawValueOrTextTokenPredicate({
2854
+ termsExpression: tokenizedSearchDeclarations.termsVar,
2855
+ isCaseSensitive
2856
+ }),
2857
+ candidateQueryVar
2858
+ };
2859
+ }
2860
+ function buildPropertyRawValueOrTextClause(params) {
2861
+ const { values, matchMode, isCaseSensitive, version, queryKey } = params;
2862
+ const declarations = [];
2863
+ const valuePredicates = [];
2864
+ const candidateQueryVars = [];
2865
+ for (const [valueIndex, value] of values.entries()) {
2866
+ const compiledClause = buildPropertyRawValueOrTextMatchClause({
2867
+ value,
2868
+ matchMode,
2869
+ isCaseSensitive,
2870
+ version,
2871
+ queryKey: `${queryKey}_${valueIndex + 1}`
2872
+ });
2873
+ declarations.push(...compiledClause.declarations);
2874
+ valuePredicates.push(compiledClause.predicate);
2875
+ if (compiledClause.candidateQueryVar != null) candidateQueryVars.push(compiledClause.candidateQueryVar);
2876
+ }
2877
+ let candidateQueryVar = null;
2878
+ if (candidateQueryVars.length > 0) {
2879
+ const candidateQueriesExpression = `(${candidateQueryVars.join(", ")})`;
2880
+ candidateQueryVar = `$query${queryKey}CandidateQuery`;
2881
+ declarations.push(`let ${candidateQueryVar} :=
2882
+ if (count(${candidateQueriesExpression}) = 1)
2883
+ then ${candidateQueriesExpression}[1]
2884
+ else if (count(${candidateQueriesExpression}) gt 1)
2885
+ then cts:or-query(${candidateQueriesExpression})
2886
+ else ()`);
2887
+ }
2888
+ return {
2889
+ declarations,
2890
+ predicate: buildOrPredicate(valuePredicates),
2891
+ candidateQueryVar
2892
+ };
2893
+ }
2662
2894
  function buildPropertyPredicateExpression(propertyPredicates) {
2663
2895
  let propertyExpression = ".//properties//property";
2664
2896
  for (const propertyPredicate of propertyPredicates) propertyExpression += `[${propertyPredicate}]`;
@@ -2715,7 +2947,18 @@ function buildPropertyClause(params) {
2715
2947
  const propertyVariable = query.propertyVariable;
2716
2948
  let candidateQueryVar = null;
2717
2949
  if (propertyVariable != null) predicateParts.push(buildPropertyLabelPredicate(propertyVariable));
2718
- if (query.dataType === "date" || query.dataType === "dateTime") predicateParts.push(buildDateRangePredicate({
2950
+ if (query.dataType === "date" || query.dataType === "dateTime") if ("value" in query && query.value != null) {
2951
+ const compiledScalarClause = buildPropertyRawValueOrTextClause({
2952
+ values: [query.value],
2953
+ matchMode: query.matchMode,
2954
+ isCaseSensitive: query.isCaseSensitive,
2955
+ version,
2956
+ queryKey
2957
+ });
2958
+ declarations.push(...compiledScalarClause.declarations);
2959
+ predicateParts.push(compiledScalarClause.predicate);
2960
+ candidateQueryVar = compiledScalarClause.candidateQueryVar;
2961
+ } else predicateParts.push(buildDateRangePredicate({
2719
2962
  from: query.from,
2720
2963
  to: query.to
2721
2964
  }));
@@ -2729,12 +2972,19 @@ function buildPropertyClause(params) {
2729
2972
  case "integer":
2730
2973
  case "decimal":
2731
2974
  case "time":
2732
- case "boolean":
2733
- predicateParts.push(buildPropertyValueAttributePredicate({
2734
- propertyValues: query.propertyValues,
2735
- attributeName: "rawValue"
2736
- }));
2975
+ case "boolean": {
2976
+ const compiledScalarClause = buildPropertyRawValueOrTextClause({
2977
+ values: query.propertyValues,
2978
+ matchMode: query.matchMode,
2979
+ isCaseSensitive: query.isCaseSensitive,
2980
+ version,
2981
+ queryKey
2982
+ });
2983
+ declarations.push(...compiledScalarClause.declarations);
2984
+ predicateParts.push(compiledScalarClause.predicate);
2985
+ candidateQueryVar = compiledScalarClause.candidateQueryVar;
2737
2986
  break;
2987
+ }
2738
2988
  case "string": {
2739
2989
  const compiledStringClause = buildPropertyStringValueClause({
2740
2990
  query,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ochre-sdk",
3
- "version": "0.21.3",
3
+ "version": "0.21.5",
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",
@@ -51,7 +51,7 @@
51
51
  "bumpp": "^11.0.1",
52
52
  "eslint": "^10.1.0",
53
53
  "prettier": "^3.8.1",
54
- "tsdown": "^0.21.6",
54
+ "tsdown": "^0.21.7",
55
55
  "typescript": "^6.0.2"
56
56
  },
57
57
  "scripts": {