@lde/search 0.27.2 → 0.28.1

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/adapter.d.ts CHANGED
@@ -9,6 +9,7 @@
9
9
  export { assertTypeInSchema, physicalFields, irAlias, displayFieldName, displayFieldPattern, displayLangOf, searchableFields, facetableFields, filterableFields, sortableFields, outputFields, isInternalField, isInlineReference, nestedReferenceType, nestedFieldName, localLookupTypeOf, identityFieldOf, identityFieldName, referenceFields, referenceTypeNamed, rootTypeNamed, inlineFramingDepth, fieldNamed, datasetField, ID_FIELD, AND_KEY, OR_KEY, labelFieldOf, labelFieldNameOf, labelSourceNameOf, labelTargetNameOf, documentKeyOf, DEFAULT_LABEL_FIELD, isRangeFacet, isAbsoluteIri, isoToUnixSeconds, unixSecondsToIso, } from './schema.js';
10
10
  export type { PhysicalFields } from './schema.js';
11
11
  export { physicalNameTokens } from './physical-name.js';
12
+ export { matchLanguage } from './language.js';
12
13
  export { joinGraph, MAX_JOIN_DEPTH } from './join-graph.js';
13
14
  export type { JoinGraph } from './join-graph.js';
14
15
  export { filterOperatorFor, filterOperator, filterOn, isWelded, isUnsatisfiable, validateQuery, assertValidQuery, resolvePath, pageForOffset, } from './query.js';
@@ -1 +1 @@
1
- {"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EACL,kBAAkB,EAClB,cAAc,EACd,OAAO,EACP,gBAAgB,EAChB,mBAAmB,EACnB,aAAa,EACb,gBAAgB,EAChB,eAAe,EACf,gBAAgB,EAChB,cAAc,EACd,YAAY,EACZ,eAAe,EACf,iBAAiB,EACjB,mBAAmB,EACnB,eAAe,EACf,iBAAiB,EACjB,eAAe,EACf,iBAAiB,EACjB,eAAe,EACf,kBAAkB,EAClB,aAAa,EACb,kBAAkB,EAClB,UAAU,EACV,YAAY,EACZ,QAAQ,EACR,OAAO,EACP,MAAM,EACN,YAAY,EACZ,gBAAgB,EAChB,iBAAiB,EACjB,iBAAiB,EACjB,aAAa,EACb,mBAAmB,EACnB,YAAY,EACZ,aAAa,EACb,gBAAgB,EAChB,gBAAgB,GACjB,MAAM,aAAa,CAAC;AACrB,YAAY,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AACxD,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAC5D,YAAY,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,EACL,iBAAiB,EACjB,cAAc,EACd,QAAQ,EACR,QAAQ,EACR,eAAe,EACf,aAAa,EACb,gBAAgB,EAChB,WAAW,EACX,aAAa,GACd,MAAM,YAAY,CAAC;AACpB,YAAY,EACV,cAAc,EACd,UAAU,EACV,YAAY,EACZ,WAAW,GACZ,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EACL,kBAAkB,EAClB,cAAc,EACd,OAAO,EACP,gBAAgB,EAChB,mBAAmB,EACnB,aAAa,EACb,gBAAgB,EAChB,eAAe,EACf,gBAAgB,EAChB,cAAc,EACd,YAAY,EACZ,eAAe,EACf,iBAAiB,EACjB,mBAAmB,EACnB,eAAe,EACf,iBAAiB,EACjB,eAAe,EACf,iBAAiB,EACjB,eAAe,EACf,kBAAkB,EAClB,aAAa,EACb,kBAAkB,EAClB,UAAU,EACV,YAAY,EACZ,QAAQ,EACR,OAAO,EACP,MAAM,EACN,YAAY,EACZ,gBAAgB,EAChB,iBAAiB,EACjB,iBAAiB,EACjB,aAAa,EACb,mBAAmB,EACnB,YAAY,EACZ,aAAa,EACb,gBAAgB,EAChB,gBAAgB,GACjB,MAAM,aAAa,CAAC;AACrB,YAAY,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AACxD,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAC5D,YAAY,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,EACL,iBAAiB,EACjB,cAAc,EACd,QAAQ,EACR,QAAQ,EACR,eAAe,EACf,aAAa,EACb,gBAAgB,EAChB,WAAW,EACX,aAAa,GACd,MAAM,YAAY,CAAC;AACpB,YAAY,EACV,cAAc,EACd,UAAU,EACV,YAAY,EACZ,WAAW,GACZ,MAAM,YAAY,CAAC"}
package/dist/adapter.js CHANGED
@@ -8,5 +8,6 @@
8
8
  */
9
9
  export { assertTypeInSchema, physicalFields, irAlias, displayFieldName, displayFieldPattern, displayLangOf, searchableFields, facetableFields, filterableFields, sortableFields, outputFields, isInternalField, isInlineReference, nestedReferenceType, nestedFieldName, localLookupTypeOf, identityFieldOf, identityFieldName, referenceFields, referenceTypeNamed, rootTypeNamed, inlineFramingDepth, fieldNamed, datasetField, ID_FIELD, AND_KEY, OR_KEY, labelFieldOf, labelFieldNameOf, labelSourceNameOf, labelTargetNameOf, documentKeyOf, DEFAULT_LABEL_FIELD, isRangeFacet, isAbsoluteIri, isoToUnixSeconds, unixSecondsToIso, } from './schema.js';
10
10
  export { physicalNameTokens } from './physical-name.js';
11
+ export { matchLanguage } from './language.js';
11
12
  export { joinGraph, MAX_JOIN_DEPTH } from './join-graph.js';
12
13
  export { filterOperatorFor, filterOperator, filterOn, isWelded, isUnsatisfiable, validateQuery, assertValidQuery, resolvePath, pageForOffset, } from './query.js';
@@ -14,11 +14,16 @@ export interface SubjectIndex {
14
14
  }
15
15
  /**
16
16
  * Iterate `quads` once, grouping each subject’s triples. Duplicate triples are
17
- * collapsed here because some SPARQL engines (e.g. QLever) do not deduplicate
18
- * CONSTRUCT output. The source is consumed a single time, so it may be a
19
- * one-shot iterable (a generator chaining several sources); the whole subject
20
- * index is held, but never more than that plus one framed subgraph at a time
21
- * downstream.
17
+ * collapsed here: some SPARQL engines (e.g. QLever) do not deduplicate
18
+ * CONSTRUCT output, and a batch merges several readers’ output unfiltered.
19
+ * Framing would fold a repeated plain value on its own, but not a repeated
20
+ * RDF list cell (it then frames as raw `rdf:first`/`rdf:rest`), and without the
21
+ * set the index and every framed subgraph would grow with the duplication
22
+ * factor rather than with the distinct triples.
23
+ *
24
+ * The source is consumed a single time, so it may be a one-shot iterable (a
25
+ * generator chaining several sources); the whole subject index is held, but
26
+ * never more than that plus one framed subgraph at a time downstream.
22
27
  *
23
28
  * Roots are never discovered here – the caller ({@link projectRoots}, from the
24
29
  * pipeline selector) already holds them and passes them to {@link frameSubjects}
@@ -1 +1 @@
1
- {"version":3,"file":"frame-by-type.d.ts","sourceRoot":"","sources":["../src/frame-by-type.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,cAAc,CAAC;AAGzC,4EAA4E;AAC5E,MAAM,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAIjD;;;;;;GAMG;AACH,MAAM,WAAW,YAAY;IAC3B,4EAA4E;IAC5E,QAAQ,CAAC,SAAS,EAAE,WAAW,CAAC,MAAM,EAAE,SAAS,IAAI,EAAE,CAAC,CAAC;CAC1D;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,QAAQ,CAAC,IAAI,CAAC,GAAG,YAAY,CAkBrE;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAuB,aAAa,CAClC,KAAK,EAAE,YAAY,EACnB,KAAK,EAAE,SAAS,MAAM,EAAE,EACxB,KAAK,SAAI,GACR,aAAa,CAAC,UAAU,CAAC,CAyB3B"}
1
+ {"version":3,"file":"frame-by-type.d.ts","sourceRoot":"","sources":["../src/frame-by-type.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,cAAc,CAAC;AAGzC,4EAA4E;AAC5E,MAAM,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAIjD;;;;;;GAMG;AACH,MAAM,WAAW,YAAY;IAC3B,4EAA4E;IAC5E,QAAQ,CAAC,SAAS,EAAE,WAAW,CAAC,MAAM,EAAE,SAAS,IAAI,EAAE,CAAC,CAAC;CAC1D;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,QAAQ,CAAC,IAAI,CAAC,GAAG,YAAY,CAkBrE;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAuB,aAAa,CAClC,KAAK,EAAE,YAAY,EACnB,KAAK,EAAE,SAAS,MAAM,EAAE,EACxB,KAAK,SAAI,GACR,aAAa,CAAC,UAAU,CAAC,CAyB3B"}
@@ -2,11 +2,16 @@ import jsonld from 'jsonld';
2
2
  const FRAME_OPTIONS = { omitGraph: false, embed: '@always' };
3
3
  /**
4
4
  * Iterate `quads` once, grouping each subject’s triples. Duplicate triples are
5
- * collapsed here because some SPARQL engines (e.g. QLever) do not deduplicate
6
- * CONSTRUCT output. The source is consumed a single time, so it may be a
7
- * one-shot iterable (a generator chaining several sources); the whole subject
8
- * index is held, but never more than that plus one framed subgraph at a time
9
- * downstream.
5
+ * collapsed here: some SPARQL engines (e.g. QLever) do not deduplicate
6
+ * CONSTRUCT output, and a batch merges several readers’ output unfiltered.
7
+ * Framing would fold a repeated plain value on its own, but not a repeated
8
+ * RDF list cell (it then frames as raw `rdf:first`/`rdf:rest`), and without the
9
+ * set the index and every framed subgraph would grow with the duplication
10
+ * factor rather than with the distinct triples.
11
+ *
12
+ * The source is consumed a single time, so it may be a one-shot iterable (a
13
+ * generator chaining several sources); the whole subject index is held, but
14
+ * never more than that plus one framed subgraph at a time downstream.
10
15
  *
11
16
  * Roots are never discovered here – the caller ({@link projectRoots}, from the
12
17
  * pipeline selector) already holds them and passes them to {@link frameSubjects}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * The entries of `candidates` that language tag `tag` matches, best first, as
3
+ * the entries themselves (so a caller can index back into `candidates`).
4
+ *
5
+ * Tags are compared per BCP-47 subtag and case-insensitively, as language
6
+ * ranges (RFC 4647). A candidate matches when it shares the tag’s primary
7
+ * language, ranked:
8
+ *
9
+ * 1. the exact tag (`nl-NL` for `nl-NL`);
10
+ * 2. the tag’s truncations, most specific first – RFC 4647 lookup, so a
11
+ * request for `nl-NL` finds a declared `nl`;
12
+ * 3. the tags it is a prefix of, in `candidates` order – RFC 4647 basic
13
+ * filtering, so a range `nl` finds `nl-NL` and `nl-BE`;
14
+ * 4. the remaining tags of the same language, longest shared prefix first, so
15
+ * a request for `nl-NL` still finds `nl-BE` rather than another language.
16
+ *
17
+ * The exact match therefore still wins where a general and a specific tag sit
18
+ * side by side (`en` and `en-GB`). `und`, the untagged bucket, matches nothing
19
+ * but `und`.
20
+ */
21
+ export declare function matchLanguage(tag: string, candidates: readonly string[]): string[];
22
+ //# sourceMappingURL=language.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"language.d.ts","sourceRoot":"","sources":["../src/language.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,aAAa,CAC3B,GAAG,EAAE,MAAM,EACX,UAAU,EAAE,SAAS,MAAM,EAAE,GAC5B,MAAM,EAAE,CA4BV"}
@@ -0,0 +1,54 @@
1
+ /**
2
+ * The entries of `candidates` that language tag `tag` matches, best first, as
3
+ * the entries themselves (so a caller can index back into `candidates`).
4
+ *
5
+ * Tags are compared per BCP-47 subtag and case-insensitively, as language
6
+ * ranges (RFC 4647). A candidate matches when it shares the tag’s primary
7
+ * language, ranked:
8
+ *
9
+ * 1. the exact tag (`nl-NL` for `nl-NL`);
10
+ * 2. the tag’s truncations, most specific first – RFC 4647 lookup, so a
11
+ * request for `nl-NL` finds a declared `nl`;
12
+ * 3. the tags it is a prefix of, in `candidates` order – RFC 4647 basic
13
+ * filtering, so a range `nl` finds `nl-NL` and `nl-BE`;
14
+ * 4. the remaining tags of the same language, longest shared prefix first, so
15
+ * a request for `nl-NL` still finds `nl-BE` rather than another language.
16
+ *
17
+ * The exact match therefore still wins where a general and a specific tag sit
18
+ * side by side (`en` and `en-GB`). `und`, the untagged bucket, matches nothing
19
+ * but `und`.
20
+ */
21
+ export function matchLanguage(tag, candidates) {
22
+ const subtags = tag.toLowerCase().split('-');
23
+ const ranked = [];
24
+ for (const candidate of candidates) {
25
+ const candidateSubtags = candidate.toLowerCase().split('-');
26
+ const shared = sharedPrefixLength(subtags, candidateSubtags);
27
+ const exact = shared === subtags.length && shared === candidateSubtags.length;
28
+ if (shared === 0 || (subtags[0] === 'und' && !exact)) {
29
+ continue;
30
+ }
31
+ const kind = exact
32
+ ? 0
33
+ : shared === candidateSubtags.length
34
+ ? 1
35
+ : shared === subtags.length
36
+ ? 2
37
+ : 3;
38
+ // Extensions keep candidate order; truncations and siblings rank by how
39
+ // much of the tag they share.
40
+ ranked.push({ candidate, kind, closeness: kind === 2 ? 0 : shared });
41
+ }
42
+ return ranked
43
+ .sort((left, right) => left.kind - right.kind || right.closeness - left.closeness)
44
+ .map(({ candidate }) => candidate);
45
+ }
46
+ function sharedPrefixLength(left, right) {
47
+ let length = 0;
48
+ while (length < left.length &&
49
+ length < right.length &&
50
+ left[length] === right[length]) {
51
+ length++;
52
+ }
53
+ return length;
54
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"project.d.ts","sourceRoot":"","sources":["../src/project.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,cAAc,CAAC;AAEzC,OAAO,EAGL,KAAK,UAAU,EAChB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAkBL,KAAK,eAAe,EAEpB,KAAK,QAAQ,EAEb,KAAK,YAAY,EACjB,KAAK,UAAU,EAEhB,MAAM,aAAa,CAAC;AAErB;;;;;;GAMG;AACH,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAEpD,+DAA+D;AAC/D,MAAM,MAAM,cAAc,GAAG;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,GAAG,aAAa,CAAC;AAE5D;;;;;;;;;;GAUG;AACH,MAAM,WAAW,iBAAkB,SAAQ,OAAO,CAChD,QAAQ,CAAC,MAAM,CAAC,eAAe,EAAE,MAAM,CAAC,CAAC,CAC1C;IACC,4CAA4C;IAC5C,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;CAC3B;AAKD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,eAAe,CAC7B,IAAI,EAAE,UAAU,EAChB,UAAU,EAAE,UAAU,EACtB,MAAM,CAAC,EAAE,YAAY,EACrB,OAAO,GAAE,iBAAyC,GACjD,cAAc,CAgBhB;AAwKD;;;;;;;;;;;;GAYG;AACH,wBAAuB,YAAY,CACjC,KAAK,EAAE,QAAQ,CAAC,IAAI,CAAC,EACrB,KAAK,EAAE,SAAS,MAAM,EAAE,EACxB,MAAM,EAAE,YAAY,EACpB,UAAU,EAAE,QAAQ,EACpB,OAAO,GAAE,iBAAyC,GACjD,aAAa,CAAC,cAAc,CAAC,CAc/B"}
1
+ {"version":3,"file":"project.d.ts","sourceRoot":"","sources":["../src/project.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,cAAc,CAAC;AAGzC,OAAO,EAGL,KAAK,UAAU,EAChB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAkBL,KAAK,eAAe,EAEpB,KAAK,QAAQ,EAEb,KAAK,YAAY,EACjB,KAAK,UAAU,EAEhB,MAAM,aAAa,CAAC;AAErB;;;;;;GAMG;AACH,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAEpD,+DAA+D;AAC/D,MAAM,MAAM,cAAc,GAAG;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,GAAG,aAAa,CAAC;AAE5D;;;;;;;;;;GAUG;AACH,MAAM,WAAW,iBAAkB,SAAQ,OAAO,CAChD,QAAQ,CAAC,MAAM,CAAC,eAAe,EAAE,MAAM,CAAC,CAAC,CAC1C;IACC,4CAA4C;IAC5C,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;CAC3B;AAKD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,eAAe,CAC7B,IAAI,EAAE,UAAU,EAChB,UAAU,EAAE,UAAU,EACtB,MAAM,CAAC,EAAE,YAAY,EACrB,OAAO,GAAE,iBAAyC,GACjD,cAAc,CAgBhB;AAwKD;;;;;;;;;;;;GAYG;AACH,wBAAuB,YAAY,CACjC,KAAK,EAAE,QAAQ,CAAC,IAAI,CAAC,EACrB,KAAK,EAAE,SAAS,MAAM,EAAE,EACxB,MAAM,EAAE,YAAY,EACpB,UAAU,EAAE,QAAQ,EACpB,OAAO,GAAE,iBAAyC,GACjD,aAAa,CAAC,cAAc,CAAC,CAc/B"}
package/dist/project.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { fold } from '@lde/text-normalization';
2
+ import { matchLanguage } from './language.js';
2
3
  import { buildSubjectIndex, frameSubjects, } from './frame-by-type.js';
3
4
  import { assertTypeInSchema, displayFieldName, documentKeyOf, fieldNamed, inheritedFacetKeys, inlineFramingDepth, irAlias, isAbsoluteIri, isInternalField, isInlineReference, isoToUnixSeconds, labelSourceNameOf, localLookupTypeOf, physicalFields, referenceTypeNamed, rootTypeNamed, } from './schema.js';
4
5
  /** The context a projection runs with when its caller supplied none. */
@@ -391,9 +392,36 @@ function applyText(document, values, field) {
391
392
  // here it simply indexes nothing.
392
393
  if (field.searchable !== undefined || field.sortable === true) {
393
394
  const names = physicalFields(field);
394
- const valuesPerLocale = field.locales.map((locale) => values
395
- .filter((value) => value.lang === locale)
396
- .map((value) => value.value));
395
+ // A literal lands under the declared locale its tag best matches as a
396
+ // language range, so `nl-NL` titles are keyed under a declared `nl`; within
397
+ // a locale, the exact tag comes first.
398
+ const valuesPerLanguage = new Map();
399
+ for (const { lang, value } of values) {
400
+ const languageValues = valuesPerLanguage.get(lang);
401
+ if (languageValues === undefined) {
402
+ valuesPerLanguage.set(lang, [value]);
403
+ }
404
+ else {
405
+ languageValues.push(value);
406
+ }
407
+ }
408
+ const languagesPerLocale = new Map();
409
+ for (const entry of valuesPerLanguage) {
410
+ const locale = matchLanguage(entry[0], field.locales)[0];
411
+ if (locale !== undefined) {
412
+ languagesPerLocale.set(locale, [
413
+ ...(languagesPerLocale.get(locale) ?? []),
414
+ entry,
415
+ ]);
416
+ }
417
+ }
418
+ const valuesPerLocale = field.locales.map((locale) => {
419
+ const entries = languagesPerLocale.get(locale) ?? [];
420
+ const ranked = matchLanguage(locale, entries.map(([language]) => language));
421
+ return entries
422
+ .sort(([left], [right]) => ranked.indexOf(left) - ranked.indexOf(right))
423
+ .flatMap(([, languageValues]) => languageValues);
424
+ });
397
425
  // What every locale’s sort key falls back to: the document’s first value
398
426
  // in `locales` order, which is the declaration’s own statement of which
399
427
  // language stands in for the others.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lde/search",
3
- "version": "0.27.2",
3
+ "version": "0.28.1",
4
4
  "description": "Engine- and domain-agnostic core for RDF-backed search: a unified declarative field model (SearchField/SearchType/SearchSchema), a neutral query IR, the SearchEngine port with logical result types, and a streaming CONSTRUCT-to-document projection. Bakes in no engine, protocol, or domain.",
5
5
  "repository": {
6
6
  "url": "git+https://github.com/ldelements/lde.git",
@@ -43,7 +43,7 @@
43
43
  "!**/*.tsbuildinfo"
44
44
  ],
45
45
  "dependencies": {
46
- "@lde/text-normalization": "^0.1.3",
46
+ "@lde/text-normalization": "^0.2.0",
47
47
  "@rdfjs/types": "^2.0.1",
48
48
  "inflection": "^3.0.2",
49
49
  "jsonld": "^9.0.0",