ochre-sdk 0.22.16 → 0.22.19

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
@@ -300,6 +300,7 @@ type Event = {
300
300
  type Interpretation = {
301
301
  date: string | null;
302
302
  number: number;
303
+ notes: Array<Note>;
303
304
  links: Array<Link>;
304
305
  properties: Array<Property>;
305
306
  bibliographies: Array<Bibliography>;
package/dist/index.mjs CHANGED
@@ -1292,7 +1292,7 @@ function parseObservation(observation) {
1292
1292
  * @returns Array of parsed Observation objects
1293
1293
  */
1294
1294
  function parseObservations(observations) {
1295
- return observations.map((obs) => parseObservation(obs));
1295
+ return observations.map((observation) => parseObservation(observation));
1296
1296
  }
1297
1297
  /**
1298
1298
  * Parses an array of raw events into standardized Event objects
@@ -1435,12 +1435,13 @@ function parseProperties(properties, language = "eng") {
1435
1435
  * @returns Array of parsed Interpretation objects
1436
1436
  */
1437
1437
  function parseInterpretations(interpretations) {
1438
- return interpretations.map((interp) => ({
1439
- date: interp.date ?? null,
1440
- number: interp.interpretationNo,
1441
- links: interp.links ? parseLinks(ensureArray(interp.links)) : [],
1442
- properties: interp.properties ? parseProperties(ensureArray(interp.properties.property)) : [],
1443
- bibliographies: interp.bibliographies ? parseBibliographies(ensureArray(interp.bibliographies.bibliography)) : []
1438
+ return interpretations.map((interpretation) => ({
1439
+ date: interpretation.date ?? null,
1440
+ number: interpretation.interpretationNo,
1441
+ notes: interpretation.notes ? parseNotes(ensureArray(interpretation.notes.note)) : [],
1442
+ links: interpretation.links ? parseLinks(ensureArray(interpretation.links)) : [],
1443
+ properties: interpretation.properties ? parseProperties(ensureArray(interpretation.properties.property)) : [],
1444
+ bibliographies: interpretation.bibliographies ? parseBibliographies(ensureArray(interpretation.bibliographies.bibliography)) : []
1444
1445
  }));
1445
1446
  }
1446
1447
  /**
@@ -2155,7 +2156,7 @@ const CTS_INCLUDES_STOP_WORDS = new Set([
2155
2156
  ]);
2156
2157
  const CTS_INCLUDES_TOKEN_WORD_REGEX = /^\p{L}+$/u;
2157
2158
  const CTS_INCLUDES_TOKEN_REGEX = /[\p{L}\p{N}*?]+/gu;
2158
- const CTS_INCLUDES_TOKEN_SPLIT_REGEX = /\W+/u;
2159
+ const CTS_EXACT_TEXT_TOKEN_REGEX = /[\p{L}\p{N}]+/gu;
2159
2160
  const CONTENT_TARGET_CONTENT_ELEMENT_PATHS = {
2160
2161
  title: [
2161
2162
  "identification",
@@ -2198,9 +2199,9 @@ function tokenizeIncludesSearchValue(params) {
2198
2199
  }
2199
2200
  return terms;
2200
2201
  }
2201
- function tokenizeExactPhraseSearchValue(params) {
2202
+ function tokenizeExactTextSearchValue(params) {
2202
2203
  const { value, isCaseSensitive } = params;
2203
- const rawTerms = (isCaseSensitive ? value : value.toLowerCase()).split(CTS_INCLUDES_TOKEN_SPLIT_REGEX);
2204
+ const rawTerms = (isCaseSensitive ? value : value.toLowerCase()).match(CTS_EXACT_TEXT_TOKEN_REGEX) ?? [];
2204
2205
  const terms = [];
2205
2206
  for (const term of rawTerms) if (term !== "") terms.push(term);
2206
2207
  return terms;
@@ -2215,7 +2216,23 @@ function shouldUseStemmedTextSearch(value) {
2215
2216
  const wildcardStrippedValue = getWildcardStrippedValue(value);
2216
2217
  return wildcardStrippedValue.length >= 3 && CTS_INCLUDES_TOKEN_WORD_REGEX.test(wildcardStrippedValue);
2217
2218
  }
2218
- function buildCtsMatchOptionsExpression(params) {
2219
+ function shouldUseFullValueFallbackForIncludes(params) {
2220
+ const { value, isCaseSensitive, terms } = params;
2221
+ if (terms.length <= 1) return false;
2222
+ const tokenSource = isCaseSensitive ? value : value.toLowerCase();
2223
+ if (/[^\p{L}\p{N}\s*?]/u.test(tokenSource)) return true;
2224
+ const rawSpaceTerms = tokenSource.trim().split(/\s+/u).filter(Boolean);
2225
+ if (rawSpaceTerms.length !== terms.length) return true;
2226
+ for (const rawTerm of rawSpaceTerms) {
2227
+ const wildcardStrippedTerm = getWildcardStrippedValue(rawTerm);
2228
+ if (hasWildcardCharacters(rawTerm)) return true;
2229
+ if (!CTS_INCLUDES_TOKEN_WORD_REGEX.test(wildcardStrippedTerm)) return true;
2230
+ if (CTS_INCLUDES_STOP_WORDS.has(rawTerm.toLowerCase())) return true;
2231
+ }
2232
+ for (const [index, rawTerm] of rawSpaceTerms.entries()) if (rawTerm !== (terms[index] ?? "")) return true;
2233
+ return false;
2234
+ }
2235
+ function buildWordQueryOptionsExpression(params) {
2219
2236
  const { matchMode, isCaseSensitive, queryFamily, language, isWildcarded } = params;
2220
2237
  const { isStemmed } = params;
2221
2238
  const options = [
@@ -2228,14 +2245,25 @@ function buildCtsMatchOptionsExpression(params) {
2228
2245
  else if (queryFamily === "text") {
2229
2246
  options.push(isStemmed ? "stemmed" : "unstemmed", isWildcarded ? "wildcarded" : "unwildcarded");
2230
2247
  if (isStemmed && language != null && language !== "") options.push(`lang=${language}`);
2231
- } else options.push("unstemmed", isWildcarded ? "wildcarded" : "unwildcarded");
2248
+ }
2232
2249
  return `(${options.map((option) => stringLiteral(option)).join(", ")})`;
2233
2250
  }
2251
+ function buildRichTextPhraseOptionsExpression(params) {
2252
+ const { isCaseSensitive } = params;
2253
+ return `(${[
2254
+ isCaseSensitive ? "case-sensitive" : "case-insensitive",
2255
+ "diacritic-sensitive",
2256
+ "punctuation-insensitive",
2257
+ "whitespace-insensitive",
2258
+ "unstemmed",
2259
+ "unwildcarded"
2260
+ ].map((option) => stringLiteral(option)).join(", ")})`;
2261
+ }
2234
2262
  function buildCtsWordQueryExpression(params) {
2235
2263
  const { value, matchMode, isCaseSensitive, queryFamily, language } = params;
2236
2264
  const isWildcarded = matchMode === "includes" && hasWildcardCharacters(value);
2237
2265
  const isStemmed = matchMode === "includes" && queryFamily === "text" && !isWildcarded && shouldUseStemmedTextSearch(value);
2238
- return `cts:word-query(${stringLiteral(value)}, ${buildCtsMatchOptionsExpression({
2266
+ return `cts:word-query(${stringLiteral(value)}, ${buildWordQueryOptionsExpression({
2239
2267
  matchMode,
2240
2268
  isCaseSensitive,
2241
2269
  queryFamily,
@@ -2244,11 +2272,31 @@ function buildCtsWordQueryExpression(params) {
2244
2272
  isStemmed
2245
2273
  })})`;
2246
2274
  }
2275
+ function buildRichTextPhraseQueryExpression(params) {
2276
+ const { value, isCaseSensitive } = params;
2277
+ return `cts:word-query(${stringLiteral(value)}, ${buildRichTextPhraseOptionsExpression({ isCaseSensitive })})`;
2278
+ }
2279
+ function buildRichTextExactQueryExpression(params) {
2280
+ const { value, isCaseSensitive } = params;
2281
+ const phraseQuery = buildRichTextPhraseQueryExpression({
2282
+ value,
2283
+ isCaseSensitive
2284
+ });
2285
+ const terms = tokenizeExactTextSearchValue({
2286
+ value,
2287
+ isCaseSensitive
2288
+ });
2289
+ if (terms.length <= 1) return phraseQuery;
2290
+ return buildOrCtsQueryExpressionInternal([phraseQuery, buildAndCtsQueryExpressionInternal(terms.map((term) => buildRichTextPhraseQueryExpression({
2291
+ value: term,
2292
+ isCaseSensitive
2293
+ })))]);
2294
+ }
2247
2295
  function buildCtsElementWordQueryExpression(params) {
2248
2296
  const { elementName, value, matchMode, isCaseSensitive, queryFamily, language } = params;
2249
2297
  const isWildcarded = matchMode === "includes" && hasWildcardCharacters(value);
2250
2298
  const isStemmed = matchMode === "includes" && queryFamily === "text" && !isWildcarded && shouldUseStemmedTextSearch(value);
2251
- return `cts:element-word-query(xs:QName("${elementName}"), ${stringLiteral(value)}, ${buildCtsMatchOptionsExpression({
2299
+ return `cts:element-word-query(xs:QName("${elementName}"), ${stringLiteral(value)}, ${buildWordQueryOptionsExpression({
2252
2300
  matchMode,
2253
2301
  isCaseSensitive,
2254
2302
  queryFamily,
@@ -2261,7 +2309,7 @@ function buildCtsElementAttributeWordQueryExpression(params) {
2261
2309
  const { elementName, attributeName, value, matchMode, isCaseSensitive, queryFamily, language } = params;
2262
2310
  const isWildcarded = matchMode === "includes" && hasWildcardCharacters(value);
2263
2311
  const isStemmed = matchMode === "includes" && queryFamily === "text" && !isWildcarded && shouldUseStemmedTextSearch(value);
2264
- return `cts:element-attribute-word-query(xs:QName("${elementName}"), xs:QName("${attributeName}"), ${stringLiteral(value)}, ${buildCtsMatchOptionsExpression({
2312
+ return `cts:element-attribute-word-query(xs:QName("${elementName}"), xs:QName("${attributeName}"), ${stringLiteral(value)}, ${buildWordQueryOptionsExpression({
2265
2313
  matchMode,
2266
2314
  isCaseSensitive,
2267
2315
  queryFamily,
@@ -2272,14 +2320,14 @@ function buildCtsElementAttributeWordQueryExpression(params) {
2272
2320
  }
2273
2321
  function buildCtsElementValueQueryExpression(params) {
2274
2322
  const { elementName, value, isCaseSensitive } = params;
2275
- return `cts:element-value-query(xs:QName("${elementName}"), ${stringLiteral(value)}, ${buildCtsMatchOptionsExpression({
2323
+ return `cts:element-value-query(xs:QName("${elementName}"), ${stringLiteral(value)}, ${buildWordQueryOptionsExpression({
2276
2324
  matchMode: "exact",
2277
2325
  isCaseSensitive
2278
2326
  })})`;
2279
2327
  }
2280
2328
  function buildCtsElementAttributeValueQueryExpression(params) {
2281
2329
  const { elementName, attributeName, value, isCaseSensitive } = params;
2282
- return `cts:element-attribute-value-query(xs:QName("${elementName}"), xs:QName("${attributeName}"), ${stringLiteral(value)}, ${buildCtsMatchOptionsExpression({
2330
+ return `cts:element-attribute-value-query(xs:QName("${elementName}"), xs:QName("${attributeName}"), ${stringLiteral(value)}, ${buildWordQueryOptionsExpression({
2283
2331
  matchMode: "exact",
2284
2332
  isCaseSensitive
2285
2333
  })})`;
@@ -2288,39 +2336,6 @@ function buildPlainElementAttributeValueQueryExpression(params) {
2288
2336
  const { elementName, attributeName, value } = params;
2289
2337
  return `cts:element-attribute-value-query(xs:QName("${elementName}"), xs:QName("${attributeName}"), ${stringLiteral(value)})`;
2290
2338
  }
2291
- function buildSearchableContentTextQueryExpression(params) {
2292
- const { value, matchMode, isCaseSensitive, language } = params;
2293
- if (matchMode === "exact") {
2294
- const phraseTerms = tokenizeExactPhraseSearchValue({
2295
- value,
2296
- isCaseSensitive
2297
- });
2298
- if (phraseTerms.length > 1) return buildAndCtsQueryExpressionInternal(phraseTerms.map((term) => buildCtsWordQueryExpression({
2299
- value: term,
2300
- matchMode,
2301
- isCaseSensitive
2302
- })));
2303
- return buildCtsWordQueryExpression({
2304
- value,
2305
- matchMode,
2306
- isCaseSensitive
2307
- });
2308
- }
2309
- return buildOrCtsQueryExpressionInternal([buildCtsElementWordQueryExpression({
2310
- elementName: "string",
2311
- value,
2312
- matchMode,
2313
- isCaseSensitive,
2314
- queryFamily: "text",
2315
- language
2316
- }), buildCtsWordQueryExpression({
2317
- value,
2318
- matchMode,
2319
- isCaseSensitive,
2320
- queryFamily: "text",
2321
- language
2322
- })]);
2323
- }
2324
2339
  function buildNestedElementQuery(elementNames, queryExpression) {
2325
2340
  let wrappedQueryExpression = queryExpression;
2326
2341
  for (const elementName of elementNames.toReversed()) wrappedQueryExpression = `cts:element-query(xs:QName("${elementName}"), ${wrappedQueryExpression})`;
@@ -2371,14 +2386,28 @@ function buildValueNotIdRefQuery() {
2371
2386
  value: "IDREF"
2372
2387
  }));
2373
2388
  }
2374
- function buildValueContentInnerQuery(params) {
2375
- const { language, value, matchMode, isCaseSensitive } = params;
2376
- return buildNestedElementQuery(["content"], buildAndCtsQueryExpressionInternal([buildContentLanguageQuery(language), buildSearchableContentTextQueryExpression({
2389
+ function buildRichTextContentQueryExpression(params) {
2390
+ const { value, matchMode, isCaseSensitive, language } = params;
2391
+ return buildAndCtsQueryExpressionInternal([buildContentLanguageQuery(language), matchMode === "exact" ? buildRichTextExactQueryExpression({
2392
+ value,
2393
+ isCaseSensitive,
2394
+ language
2395
+ }) : buildCtsWordQueryExpression({
2377
2396
  value,
2378
2397
  matchMode,
2379
2398
  isCaseSensitive,
2399
+ queryFamily: "text",
2380
2400
  language
2381
- })]));
2401
+ })]);
2402
+ }
2403
+ function buildValueContentInnerQuery(params) {
2404
+ const { language, value, matchMode, isCaseSensitive } = params;
2405
+ return buildNestedElementQuery(["content"], buildRichTextContentQueryExpression({
2406
+ language,
2407
+ value,
2408
+ matchMode,
2409
+ isCaseSensitive
2410
+ }));
2382
2411
  }
2383
2412
  function buildValueDirectTextInnerQuery(params) {
2384
2413
  const { value, matchMode, isCaseSensitive } = params;
@@ -2418,35 +2447,22 @@ function buildNotesQueryExpression(params) {
2418
2447
  "notes",
2419
2448
  "note",
2420
2449
  "content"
2421
- ], buildAndCtsQueryExpressionInternal([buildContentLanguageQuery(language), buildOrCtsQueryExpressionInternal([matchMode === "exact" ? buildCtsElementAttributeValueQueryExpression({
2422
- elementName: "content",
2423
- attributeName: "title",
2424
- value,
2425
- isCaseSensitive
2426
- }) : buildCtsElementAttributeWordQueryExpression({
2427
- elementName: "content",
2428
- attributeName: "title",
2450
+ ], buildRichTextContentQueryExpression({
2429
2451
  value,
2430
2452
  matchMode,
2431
2453
  isCaseSensitive,
2432
- queryFamily: "text",
2433
2454
  language
2434
- }), buildSearchableContentTextQueryExpression({
2435
- value,
2436
- matchMode,
2437
- isCaseSensitive,
2438
- language
2439
- })])]));
2455
+ }));
2440
2456
  }
2441
2457
  function buildContentTargetQueryExpression(params) {
2442
2458
  const { target, value, matchMode, isCaseSensitive, language } = params;
2443
2459
  const contentElementPath = CONTENT_TARGET_CONTENT_ELEMENT_PATHS[target];
2444
- return buildNestedElementQuery(contentElementPath, buildAndCtsQueryExpressionInternal([buildContentLanguageQuery(language), buildSearchableContentTextQueryExpression({
2460
+ return buildNestedElementQuery(contentElementPath, buildRichTextContentQueryExpression({
2445
2461
  value,
2446
2462
  matchMode,
2447
2463
  isCaseSensitive,
2448
2464
  language
2449
- })]));
2465
+ }));
2450
2466
  }
2451
2467
  function buildPropertyQueryExpression(params) {
2452
2468
  const { propertyVariable, queryExpression } = params;
@@ -2454,6 +2470,31 @@ function buildPropertyQueryExpression(params) {
2454
2470
  if (propertyVariable != null) propertyQueryExpressions.unshift(buildPropertyLabelQuery(propertyVariable));
2455
2471
  return buildNestedElementQuery(["properties", "property"], buildAndCtsQueryExpressionInternal(propertyQueryExpressions));
2456
2472
  }
2473
+ function buildPropertyTextMatchQueryExpression(params) {
2474
+ const { propertyVariable, valueFilters = [], contentQueryExpression, rawValueQueryExpression, bareValueQueryExpression } = params;
2475
+ const letBindings = [];
2476
+ const valueMatchReferences = [];
2477
+ if (contentQueryExpression != null) {
2478
+ letBindings.push(`let $contentQuery := ${contentQueryExpression}`);
2479
+ valueMatchReferences.push("$contentQuery");
2480
+ }
2481
+ if (rawValueQueryExpression != null) {
2482
+ letBindings.push(`let $rawValueQuery := ${rawValueQueryExpression}`);
2483
+ valueMatchReferences.push("$rawValueQuery");
2484
+ }
2485
+ if (bareValueQueryExpression != null) {
2486
+ letBindings.push(`let $bareValueQuery := ${bareValueQueryExpression}`);
2487
+ valueMatchReferences.push("$bareValueQuery");
2488
+ }
2489
+ const valueQueryExpressions = [...valueFilters];
2490
+ if (valueMatchReferences.length > 0) valueQueryExpressions.push(buildOrCtsQueryExpressionInternal(valueMatchReferences));
2491
+ const propertyQueryExpressions = [];
2492
+ if (propertyVariable != null) propertyQueryExpressions.push(buildPropertyLabelQuery(propertyVariable));
2493
+ propertyQueryExpressions.push(buildNestedElementQuery(["value"], buildAndCtsQueryExpressionInternal(valueQueryExpressions)));
2494
+ const propertyQueryExpression = buildNestedElementQuery(["properties", "property"], buildAndCtsQueryExpressionInternal(propertyQueryExpressions));
2495
+ if (letBindings.length === 0) return propertyQueryExpression;
2496
+ return `(${letBindings.join("\n ")}\n return ${propertyQueryExpression})`;
2497
+ }
2457
2498
  function buildPropertyPresenceQueryExpression(params) {
2458
2499
  return buildPropertyQueryExpression({
2459
2500
  propertyVariable: params.propertyVariable,
@@ -2462,14 +2503,15 @@ function buildPropertyPresenceQueryExpression(params) {
2462
2503
  }
2463
2504
  function buildPropertyStringQueryExpression(params) {
2464
2505
  const { propertyVariable, value, matchMode, isCaseSensitive, language } = params;
2465
- return buildPropertyQueryExpression({
2506
+ return buildPropertyTextMatchQueryExpression({
2466
2507
  propertyVariable,
2467
- queryExpression: buildNestedElementQuery(["value"], buildAndCtsQueryExpressionInternal([buildValueNotInheritedQuery(), buildValueContentInnerQuery({
2508
+ valueFilters: [buildValueNotInheritedQuery()],
2509
+ contentQueryExpression: buildValueContentInnerQuery({
2468
2510
  language,
2469
2511
  value,
2470
2512
  matchMode,
2471
2513
  isCaseSensitive
2472
- })]))
2514
+ })
2473
2515
  });
2474
2516
  }
2475
2517
  function buildPropertyScalarQueryExpression(params) {
@@ -2489,34 +2531,25 @@ function buildPropertyScalarQueryExpression(params) {
2489
2531
  }
2490
2532
  function buildPropertyAllQueryExpression(params) {
2491
2533
  const { query, value, matchMode } = params;
2492
- if (matchMode === "includes") return buildPropertyQueryExpression({
2493
- propertyVariable: query.propertyVariable,
2494
- queryExpression: buildNestedElementQuery(["value"], buildAndCtsQueryExpressionInternal([buildValueNotIdRefQuery(), buildOrCtsQueryExpressionInternal([
2495
- buildValueContentInnerQuery({
2496
- language: query.language,
2497
- value,
2498
- matchMode,
2499
- isCaseSensitive: query.isCaseSensitive
2500
- }),
2501
- buildValueRawValueInnerQuery({
2502
- value,
2503
- matchMode,
2504
- isCaseSensitive: query.isCaseSensitive
2505
- }),
2506
- buildValueDirectTextInnerQuery({
2507
- value,
2508
- matchMode,
2509
- isCaseSensitive: query.isCaseSensitive
2510
- })
2511
- ])]))
2512
- });
2513
- return buildPropertyQueryExpression({
2534
+ return buildPropertyTextMatchQueryExpression({
2514
2535
  propertyVariable: query.propertyVariable,
2515
- queryExpression: buildNestedElementQuery(["value"], buildAndCtsQueryExpressionInternal([buildValueNotIdRefQuery(), buildCtsWordQueryExpression({
2536
+ valueFilters: [buildValueNotIdRefQuery()],
2537
+ contentQueryExpression: buildValueContentInnerQuery({
2538
+ language: query.language,
2516
2539
  value,
2517
2540
  matchMode,
2518
2541
  isCaseSensitive: query.isCaseSensitive
2519
- })]))
2542
+ }),
2543
+ rawValueQueryExpression: buildValueRawValueInnerQuery({
2544
+ value,
2545
+ matchMode,
2546
+ isCaseSensitive: query.isCaseSensitive
2547
+ }),
2548
+ bareValueQueryExpression: buildValueDirectTextInnerQuery({
2549
+ value,
2550
+ matchMode,
2551
+ isCaseSensitive: query.isCaseSensitive
2552
+ })
2520
2553
  });
2521
2554
  }
2522
2555
  function buildPropertyIdRefQueryExpression(params) {
@@ -2623,35 +2656,181 @@ function buildLeafValueQueryExpression(params) {
2623
2656
  }
2624
2657
  }
2625
2658
  }
2626
- function buildLeafQueryExpression(query) {
2659
+ function indentBlock(value, spaces) {
2660
+ const prefix = " ".repeat(spaces);
2661
+ return value.split("\n").map((line) => line === "" ? line : `${prefix}${line}`).join("\n");
2662
+ }
2663
+ function createQueryCompilerContext() {
2664
+ return {
2665
+ nextHelperSerial: 1,
2666
+ helperNamesByKey: /* @__PURE__ */ new Map(),
2667
+ helperDeclarations: []
2668
+ };
2669
+ }
2670
+ function registerConstantHelper(params) {
2671
+ const { context, key, bodyExpression } = params;
2672
+ const existingName = context.helperNamesByKey.get(key);
2673
+ if (existingName != null) return {
2674
+ name: existingName,
2675
+ callExpression: `${existingName}()`
2676
+ };
2677
+ const helperName = `local:queryHelper${context.nextHelperSerial}`;
2678
+ context.nextHelperSerial += 1;
2679
+ context.helperNamesByKey.set(key, helperName);
2680
+ context.helperDeclarations.push(`declare function ${helperName}() as cts:query {\n${indentBlock(bodyExpression, 2)}\n};`);
2681
+ return {
2682
+ name: helperName,
2683
+ callExpression: `${helperName}()`
2684
+ };
2685
+ }
2686
+ function replaceSampleValueLiteral(expression, sampleValue, valueReference) {
2687
+ return expression.replaceAll(stringLiteral(sampleValue), valueReference);
2688
+ }
2689
+ function registerParameterizedHelper(params) {
2690
+ const { context, key, bodyExpression } = params;
2691
+ const existingName = context.helperNamesByKey.get(key);
2692
+ if (existingName != null) return {
2693
+ name: existingName,
2694
+ call: (valueExpression) => `${existingName}(${valueExpression})`
2695
+ };
2696
+ const helperName = `local:queryHelper${context.nextHelperSerial}`;
2697
+ context.nextHelperSerial += 1;
2698
+ context.helperNamesByKey.set(key, helperName);
2699
+ context.helperDeclarations.push(`declare function ${helperName}($value as xs:string) as cts:query {\n${indentBlock(bodyExpression, 2)}\n};`);
2700
+ return {
2701
+ name: helperName,
2702
+ call: (valueExpression) => `${helperName}(${valueExpression})`
2703
+ };
2704
+ }
2705
+ function getLeafHelperKey(params) {
2706
+ const { query, matchMode, value } = params;
2707
+ switch (query.target) {
2708
+ case "string":
2709
+ case "title":
2710
+ case "description":
2711
+ case "image":
2712
+ case "periods":
2713
+ case "bibliography":
2714
+ case "notes": return [
2715
+ "leaf",
2716
+ matchMode,
2717
+ query.target,
2718
+ value,
2719
+ query.isCaseSensitive ? "case-sensitive" : "case-insensitive",
2720
+ query.language
2721
+ ].join("|");
2722
+ case "property": return [
2723
+ "leaf",
2724
+ matchMode,
2725
+ query.target,
2726
+ query.dataType,
2727
+ query.propertyVariable ?? "",
2728
+ value,
2729
+ query.isCaseSensitive ? "case-sensitive" : "case-insensitive",
2730
+ query.language
2731
+ ].join("|");
2732
+ }
2733
+ }
2734
+ function registerLeafHelper(params) {
2735
+ const { context, query, matchMode, value } = params;
2736
+ return registerConstantHelper({
2737
+ context,
2738
+ key: getLeafHelperKey({
2739
+ query,
2740
+ matchMode,
2741
+ value
2742
+ }),
2743
+ bodyExpression: buildLeafValueQueryExpression({
2744
+ query,
2745
+ value,
2746
+ matchMode
2747
+ })
2748
+ });
2749
+ }
2750
+ function getIncludesLeafHelperKey(params) {
2751
+ const { query, value } = params;
2752
+ const isWildcarded = hasWildcardCharacters(value);
2753
+ const isStemmed = !isWildcarded && shouldUseStemmedTextSearch(value);
2754
+ switch (query.target) {
2755
+ case "string":
2756
+ case "title":
2757
+ case "description":
2758
+ case "image":
2759
+ case "periods":
2760
+ case "bibliography":
2761
+ case "notes": return [
2762
+ "includes-helper",
2763
+ query.target,
2764
+ query.isCaseSensitive ? "case-sensitive" : "case-insensitive",
2765
+ query.language,
2766
+ isWildcarded ? "wildcarded" : "unwildcarded",
2767
+ isStemmed ? "stemmed" : "unstemmed"
2768
+ ].join("|");
2769
+ case "property": return [
2770
+ "includes-helper",
2771
+ query.target,
2772
+ query.dataType,
2773
+ query.propertyVariable ?? "",
2774
+ query.isCaseSensitive ? "case-sensitive" : "case-insensitive",
2775
+ query.language,
2776
+ isWildcarded ? "wildcarded" : "unwildcarded",
2777
+ isStemmed ? "stemmed" : "unstemmed"
2778
+ ].join("|");
2779
+ }
2780
+ }
2781
+ function registerIncludesLeafHelper(params) {
2782
+ const { context, query, sampleValue } = params;
2783
+ return registerParameterizedHelper({
2784
+ context,
2785
+ key: getIncludesLeafHelperKey({
2786
+ query,
2787
+ value: sampleValue
2788
+ }),
2789
+ bodyExpression: replaceSampleValueLiteral(buildLeafValueQueryExpression({
2790
+ query,
2791
+ value: sampleValue,
2792
+ matchMode: "includes"
2793
+ }), sampleValue, "$value")
2794
+ });
2795
+ }
2796
+ function buildLeafQueryExpression(context, query) {
2627
2797
  if (query.target === "property" && query.dataType !== "date" && query.dataType !== "dateTime" && !("value" in query) && query.propertyVariable != null) return buildPropertyPresenceQueryExpression({ propertyVariable: query.propertyVariable });
2628
2798
  if (query.target === "property" && (query.dataType === "date" || query.dataType === "dateTime") && query.value == null) return buildPropertyDateRangeQueryExpression(query);
2629
2799
  const searchValue = getLeafSearchValue(query);
2630
2800
  if (searchValue == null) throw new Error("Missing searchable value for query leaf");
2631
- if (query.matchMode === "exact") return buildLeafValueQueryExpression({
2801
+ const exactHelper = registerLeafHelper({
2802
+ context,
2632
2803
  query,
2633
- value: searchValue,
2634
- matchMode: "exact"
2804
+ matchMode: "exact",
2805
+ value: searchValue
2635
2806
  });
2807
+ if (query.matchMode === "exact") return exactHelper.callExpression;
2636
2808
  const terms = tokenizeIncludesSearchValue({
2637
2809
  value: searchValue,
2638
2810
  isCaseSensitive: query.isCaseSensitive
2639
2811
  });
2640
- const fullValueQueryExpression = buildLeafValueQueryExpression({
2641
- query,
2642
- value: searchValue,
2643
- matchMode: "exact"
2644
- });
2645
2812
  if (terms.length === 0) return "cts:false-query()";
2646
- const termQueryExpressions = [];
2647
- for (const term of terms) termQueryExpressions.push(buildLeafValueQueryExpression({
2813
+ const includesHelper = registerIncludesLeafHelper({
2814
+ context,
2648
2815
  query,
2649
- value: term,
2650
- matchMode: "includes"
2651
- }));
2652
- const tokenizedQueryExpression = buildAndCtsQueryExpressionInternal(termQueryExpressions);
2653
- if (terms.length === 1) return tokenizedQueryExpression;
2654
- return buildOrCtsQueryExpressionInternal([fullValueQueryExpression, tokenizedQueryExpression]);
2816
+ sampleValue: terms[0] ?? ""
2817
+ });
2818
+ const tokenizedHelperCalls = [];
2819
+ for (const term of terms) {
2820
+ const termHelper = term === (terms[0] ?? "") ? includesHelper : registerIncludesLeafHelper({
2821
+ context,
2822
+ query,
2823
+ sampleValue: term
2824
+ });
2825
+ tokenizedHelperCalls.push(termHelper.call(stringLiteral(term)));
2826
+ }
2827
+ const tokenizedQueryExpression = buildAndCtsQueryExpressionInternal(tokenizedHelperCalls);
2828
+ if (!shouldUseFullValueFallbackForIncludes({
2829
+ value: searchValue,
2830
+ isCaseSensitive: query.isCaseSensitive,
2831
+ terms
2832
+ })) return tokenizedQueryExpression;
2833
+ return buildOrCtsQueryExpressionInternal([exactHelper.callExpression, tokenizedQueryExpression]);
2655
2834
  }
2656
2835
  function getGroupableIncludesValue(query) {
2657
2836
  if (query.matchMode !== "includes" || query.isNegated === true) return null;
@@ -2691,21 +2870,7 @@ function getCompatibleIncludesGroupLeaves(query) {
2691
2870
  for (const leafQuery of leafQueries) if (getGroupableIncludesValue(leafQuery) !== groupValue || leafQuery.isCaseSensitive !== firstQuery.isCaseSensitive || leafQuery.language !== firstQuery.language) return null;
2692
2871
  return leafQueries;
2693
2872
  }
2694
- function buildIncludesGroupMember(query) {
2695
- return {
2696
- buildTermQuery: (term) => buildLeafValueQueryExpression({
2697
- query,
2698
- value: term,
2699
- matchMode: "includes"
2700
- }),
2701
- buildFullValueQuery: (value) => buildLeafValueQueryExpression({
2702
- query,
2703
- value,
2704
- matchMode: "exact"
2705
- })
2706
- };
2707
- }
2708
- function buildIncludesGroupQueryExpression(queries) {
2873
+ function buildIncludesGroupQueryExpression(context, queries) {
2709
2874
  const firstQuery = queries[0];
2710
2875
  if (firstQuery == null) throw new Error("Cannot build an includes group without queries");
2711
2876
  const groupValue = getGroupableIncludesValue(firstQuery);
@@ -2715,28 +2880,56 @@ function buildIncludesGroupQueryExpression(queries) {
2715
2880
  isCaseSensitive: firstQuery.isCaseSensitive
2716
2881
  });
2717
2882
  if (terms.length === 0) return "cts:false-query()";
2718
- const members = queries.map((query) => buildIncludesGroupMember(query));
2719
- const perTermQueryExpressions = [];
2720
- const fullValueFieldQueryExpressions = [];
2721
- for (const member of members) fullValueFieldQueryExpressions.push(member.buildFullValueQuery(groupValue));
2883
+ const tokenizedHelperCalls = [];
2722
2884
  for (const term of terms) {
2723
- const fieldQueryExpressions = [];
2724
- for (const member of members) fieldQueryExpressions.push(member.buildTermQuery(term));
2725
- perTermQueryExpressions.push(buildOrCtsQueryExpressionInternal(fieldQueryExpressions));
2885
+ const memberHelpers = queries.map((query) => registerIncludesLeafHelper({
2886
+ context,
2887
+ query,
2888
+ sampleValue: term
2889
+ }));
2890
+ const termGroupHelper = registerParameterizedHelper({
2891
+ context,
2892
+ key: [
2893
+ "group",
2894
+ "includes",
2895
+ ...memberHelpers.map((helper) => helper.name)
2896
+ ].join("|"),
2897
+ bodyExpression: buildOrCtsQueryExpressionInternal(memberHelpers.map((helper) => helper.call("$value")))
2898
+ });
2899
+ tokenizedHelperCalls.push(termGroupHelper.call(stringLiteral(term)));
2726
2900
  }
2727
- const tokenizedGroupQueryExpression = buildAndCtsQueryExpressionInternal(perTermQueryExpressions);
2728
- if (terms.length === 1) return tokenizedGroupQueryExpression;
2729
- return buildOrCtsQueryExpressionInternal([buildOrCtsQueryExpressionInternal(fullValueFieldQueryExpressions), tokenizedGroupQueryExpression]);
2730
- }
2731
- function buildQueryNode(query) {
2901
+ const tokenizedQueryExpression = buildAndCtsQueryExpressionInternal(tokenizedHelperCalls);
2902
+ if (!shouldUseFullValueFallbackForIncludes({
2903
+ value: groupValue,
2904
+ isCaseSensitive: firstQuery.isCaseSensitive,
2905
+ terms
2906
+ })) return tokenizedQueryExpression;
2907
+ const exactMemberHelpers = queries.map((query) => registerLeafHelper({
2908
+ context,
2909
+ query,
2910
+ matchMode: "exact",
2911
+ value: groupValue
2912
+ }));
2913
+ return buildOrCtsQueryExpressionInternal([registerConstantHelper({
2914
+ context,
2915
+ key: [
2916
+ "group",
2917
+ "exact",
2918
+ groupValue,
2919
+ ...exactMemberHelpers.map((helper) => helper.name)
2920
+ ].join("|"),
2921
+ bodyExpression: buildOrCtsQueryExpressionInternal(exactMemberHelpers.map((helper) => helper.callExpression))
2922
+ }).callExpression, tokenizedQueryExpression]);
2923
+ }
2924
+ function buildQueryNode(context, query) {
2732
2925
  if (isQueryLeaf(query)) {
2733
- const queryExpression = buildLeafQueryExpression(query);
2926
+ const queryExpression = buildLeafQueryExpression(context, query);
2734
2927
  return query.isNegated === true ? buildNotCtsQueryExpression(queryExpression) : queryExpression;
2735
2928
  }
2736
2929
  const optimizedIncludesGroupQueries = getCompatibleIncludesGroupLeaves(query);
2737
- if (optimizedIncludesGroupQueries != null) return buildIncludesGroupQueryExpression(optimizedIncludesGroupQueries);
2930
+ if (optimizedIncludesGroupQueries != null) return buildIncludesGroupQueryExpression(context, optimizedIncludesGroupQueries);
2738
2931
  const childQueryExpressions = [];
2739
- for (const childQuery of getQueryGroupChildren(query)) childQueryExpressions.push(buildQueryNode(childQuery));
2932
+ for (const childQuery of getQueryGroupChildren(query)) childQueryExpressions.push(buildQueryNode(context, childQuery));
2740
2933
  return getQueryGroupOperator(query) === "and" ? buildAndCtsQueryExpressionInternal(childQueryExpressions) : buildOrCtsQueryExpressionInternal(childQueryExpressions);
2741
2934
  }
2742
2935
  function buildBelongsToCollectionQueryExpression(belongsToCollectionScopeUuids, belongsToCollectionPropertyVariableUuid) {
@@ -2754,8 +2947,16 @@ function buildBelongsToCollectionQueryExpression(belongsToCollectionScopeUuids,
2754
2947
  }
2755
2948
  function buildQueryPlan(params) {
2756
2949
  const { queries } = params;
2757
- if (queries == null) return { queryExpression: null };
2758
- return { queryExpression: buildQueryNode(queries) };
2950
+ if (queries == null) return {
2951
+ prolog: "",
2952
+ queryExpression: null
2953
+ };
2954
+ const context = createQueryCompilerContext();
2955
+ const queryExpression = buildQueryNode(context, queries);
2956
+ return {
2957
+ prolog: context.helperDeclarations.join("\n\n"),
2958
+ queryExpression
2959
+ };
2759
2960
  }
2760
2961
  //#endregion
2761
2962
  //#region src/utils/fetchers/set/items.ts
@@ -2844,8 +3045,8 @@ function buildOrderedItemsClause(sort) {
2844
3045
  function buildXQuery$1(params) {
2845
3046
  const { queries, sort, setScopeUuids, belongsToCollectionScopeUuids, page, pageSize } = params;
2846
3047
  const startPosition = (page - 1) * pageSize + 1;
2847
- const endPosition = page * pageSize;
2848
- const baseItemsExpression = `doc()/ochre${`/set[(${setScopeUuids.map((uuid) => `@uuid="${uuid}"`).join(" or ")})]/items/*`}`;
3048
+ const setScopeDeclaration = `declare variable $setScopeUuids := (${setScopeUuids.map((uuid) => stringLiteral(uuid)).join(", ")});`;
3049
+ const baseItemsExpression = "doc()/ochre/set[@uuid = $setScopeUuids]/items/*";
2849
3050
  const compiledQueryPlan = buildQueryPlan({ queries });
2850
3051
  const itemsQueryExpressions = [];
2851
3052
  const belongsToCollectionQueryExpression = buildBelongsToCollectionQueryExpression(belongsToCollectionScopeUuids, BELONGS_TO_COLLECTION_UUID);
@@ -2853,17 +3054,22 @@ function buildXQuery$1(params) {
2853
3054
  if (belongsToCollectionQueryExpression != null) itemsQueryExpressions.push(belongsToCollectionQueryExpression);
2854
3055
  const itemsQueryExpression = buildAndCtsQueryExpression(itemsQueryExpressions);
2855
3056
  const orderedItemsClause = buildOrderedItemsClause(sort);
2856
- return `<ochre>{${`${itemsQueryExpression == null ? `let $items := ${baseItemsExpression}` : `let $items := ${baseItemsExpression}[cts:contains(., ${itemsQueryExpression})]`}
3057
+ const xqueryDeclarations = ["xquery version \"1.0-ml\";", setScopeDeclaration];
3058
+ if (compiledQueryPlan.prolog !== "") xqueryDeclarations.push(compiledQueryPlan.prolog);
3059
+ const itemsClause = itemsQueryExpression == null ? `let $items := ${baseItemsExpression}` : `let $query := ${itemsQueryExpression}
3060
+ let $items := cts:search(${baseItemsExpression}, $query)`;
3061
+ return `${xqueryDeclarations.join("\n\n")}
2857
3062
 
3063
+ <ochre>{
3064
+ ${itemsClause}
2858
3065
  let $totalCount := count($items)
2859
3066
  ${orderedItemsClause}
3067
+ let $pagedItems := subsequence($orderedItems, ${startPosition}, ${pageSize})
2860
3068
 
2861
3069
  return <items totalCount="{$totalCount}" page="${page}" pageSize="${pageSize}">{
2862
- for $item in $orderedItems[position() ge ${startPosition} and position() le ${endPosition}]
2863
- return element { node-name($item) } {
2864
- $item/@*, $item/node()
2865
- }
2866
- }</items>`}}</ochre>`;
3070
+ $pagedItems
3071
+ }</items>
3072
+ }</ochre>`;
2867
3073
  }
2868
3074
  /**
2869
3075
  * Fetches and parses Set items from the OCHRE API
@@ -3154,9 +3360,9 @@ const responseSchema = z.object({ result: z.union([z.object({ ochre: z.object({
3154
3360
  */
3155
3361
  function buildXQuery(params) {
3156
3362
  const { setScopeUuids, belongsToCollectionScopeUuids, queries, propertyVariableUuids, attributes, isLimitedToLeafPropertyValues } = params;
3157
- let setScopeFilter = "/set/items/*";
3158
- if (setScopeUuids.length > 0) setScopeFilter = `/set[(${setScopeUuids.map((uuid) => `@uuid="${uuid}"`).join(" or ")})]/items/*`;
3159
- const baseItemsExpression = `doc()/ochre${setScopeFilter}`;
3363
+ const setScopeValues = setScopeUuids.map((uuid) => stringLiteral(uuid));
3364
+ const setScopeDeclaration = setScopeValues.length > 0 ? `declare variable $setScopeUuids := (${setScopeValues.join(", ")});` : "";
3365
+ const baseItemsExpression = setScopeValues.length > 0 ? "doc()/ochre/set[@uuid = $setScopeUuids]/items/*" : "doc()/ochre/set/items/*";
3160
3366
  const compiledQueryPlan = buildQueryPlan({ queries: getItemFilterQueriesFromPropertyValueQueries(queries) });
3161
3367
  const itemsQueryExpressions = [];
3162
3368
  const belongsToCollectionQueryExpression = buildBelongsToCollectionQueryExpression(belongsToCollectionScopeUuids, BELONGS_TO_COLLECTION_UUID);
@@ -3166,6 +3372,9 @@ function buildXQuery(params) {
3166
3372
  const valueFilter = isLimitedToLeafPropertyValues ? "[not(@i)]" : "";
3167
3373
  const queryBlocks = [];
3168
3374
  const returnedSequences = [];
3375
+ const xqueryDeclarations = ["xquery version \"1.0-ml\";"];
3376
+ if (setScopeDeclaration !== "") xqueryDeclarations.push(setScopeDeclaration);
3377
+ if (compiledQueryPlan.prolog !== "") xqueryDeclarations.push(compiledQueryPlan.prolog);
3169
3378
  if (propertyVariableUuids.length > 0) {
3170
3379
  const propertyVariableFilters = propertyVariableUuids.map((uuid) => `@uuid="${uuid}"`).join(" or ");
3171
3380
  queryBlocks.push(`let $matching-props := $items//property[label[${propertyVariableFilters}]]
@@ -3198,11 +3407,16 @@ let $property-values :=
3198
3407
  return <attributeValue attributeType="periods" itemUuid="{$item/@uuid}" content="{$label}" />`);
3199
3408
  returnedSequences.push("$period-values");
3200
3409
  }
3201
- return `<ochre>{${`${itemsQueryExpression == null ? `let $items := ${baseItemsExpression}` : `let $items := ${baseItemsExpression}[cts:contains(., ${itemsQueryExpression})]`}
3410
+ const itemsClause = itemsQueryExpression == null ? `let $items := ${baseItemsExpression}` : `let $query := ${itemsQueryExpression}
3411
+ let $items := cts:search(${baseItemsExpression}, $query)`;
3412
+ return `${xqueryDeclarations.join("\n\n")}
3202
3413
 
3414
+ <ochre>{
3415
+ ${itemsClause}
3203
3416
  ${queryBlocks.join("\n\n")}
3204
3417
 
3205
- return (${returnedSequences.join(", ")})`}}</ochre>`;
3418
+ return (${returnedSequences.join(", ")})
3419
+ }</ochre>`;
3206
3420
  }
3207
3421
  /**
3208
3422
  * Fetches and parses Set property values from the OCHRE API
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ochre-sdk",
3
- "version": "0.22.16",
3
+ "version": "0.22.19",
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",
@@ -52,7 +52,7 @@
52
52
  "@antfu/eslint-config": "^8.2.0",
53
53
  "@types/node": "^24.12.2",
54
54
  "bumpp": "^11.0.1",
55
- "eslint": "^10.2.0",
55
+ "eslint": "^10.2.1",
56
56
  "prettier": "^3.8.3",
57
57
  "tsdown": "^0.21.9",
58
58
  "typescript": "^6.0.3",