@lde/search-pipeline 0.25.0 → 0.26.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.
@@ -33,6 +33,15 @@ export interface ExtractionOptions {
33
33
  * nested template (`{ ?root <…/ref> ?r . ?r <…/field> ?v }`), recursing into
34
34
  * the reference type to the schema’s declared depth. The referent-binding hop
35
35
  * uses the source `path`; the emitted triples use the minted aliases.
36
+ * - **`local` lookups** ({@link ReferenceStrategy.local}): a reference that also
37
+ * stores what this document states about its referent gets its branch
38
+ * extended with the **target Root Type’s own** fields, read off the referent
39
+ * and emitted under that type’s aliases – the same recursion an inline
40
+ * reference makes, through a Root Type rather than a Reference Type, and the
41
+ * same reach {@link inlineFramingDepth} already frames for. `OPTIONAL`, so a
42
+ * referent the document states nothing about keeps its row and still stores
43
+ * its id. It subsumes the key hop below, the key field being one of the
44
+ * target’s own.
36
45
  * - **references into a keyed type**: a reference naming a target that declares
37
46
  * a `key` gets its branch extended with an `OPTIONAL` hop reading the
38
47
  * referent’s key field, emitted under the **target’s** alias for it – so the
@@ -1 +1 @@
1
- {"version":3,"file":"extraction.d.ts","sourceRoot":"","sources":["../src/extraction.ts"],"names":[],"mappings":"AAAA,OAAO,EAKL,KAAK,cAAc,EAIpB,MAAM,2BAA2B,CAAC;AAWnC,OAAO,KAAK,EAIV,YAAY,EACZ,UAAU,EACX,MAAM,aAAa,CAAC;AAMrB,2CAA2C;AAC3C,MAAM,WAAW,iBAAiB;IAChC;;;;;;;OAOG;IACH,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAC;CACnC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,wBAAgB,eAAe,CAC7B,UAAU,EAAE,UAAU,EACtB,MAAM,EAAE,YAAY,EACpB,OAAO,GAAE,iBAAsB,GAC9B,cAAc,CAqBhB;AAED,+EAA+E;AAC/E,wBAAgB,qBAAqB,CACnC,UAAU,EAAE,UAAU,EACtB,MAAM,EAAE,YAAY,EACpB,OAAO,CAAC,EAAE,iBAAiB,GAC1B,MAAM,CAER"}
1
+ {"version":3,"file":"extraction.d.ts","sourceRoot":"","sources":["../src/extraction.ts"],"names":[],"mappings":"AAAA,OAAO,EAKL,KAAK,cAAc,EAIpB,MAAM,2BAA2B,CAAC;AAYnC,OAAO,KAAK,EAIV,YAAY,EACZ,UAAU,EACX,MAAM,aAAa,CAAC;AAMrB,2CAA2C;AAC3C,MAAM,WAAW,iBAAiB;IAChC;;;;;;;OAOG;IACH,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAC;CACnC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AACH,wBAAgB,eAAe,CAC7B,UAAU,EAAE,UAAU,EACtB,MAAM,EAAE,YAAY,EACpB,OAAO,GAAE,iBAAsB,GAC9B,cAAc,CAqBhB;AAED,+EAA+E;AAC/E,wBAAgB,qBAAqB,CACnC,UAAU,EAAE,UAAU,EACtB,MAAM,EAAE,YAAY,EACpB,OAAO,CAAC,EAAE,iBAAiB,GAC1B,MAAM,CAER"}
@@ -1,7 +1,7 @@
1
1
  import { AstFactory, } from '@traqula/rules-sparql-1-1';
2
2
  import { Parser } from '@traqula/parser-sparql-1-1';
3
3
  import { Generator } from '@traqula/generator-sparql-1-1';
4
- import { fieldNamed, irAlias, isInlineReference, labelSourceNameOf, referenceTypeNamed, rootTypeNamed, } from '@lde/search/adapter';
4
+ import { fieldNamed, irAlias, isInlineReference, labelSourceNameOf, localLookupTypeOf, referenceTypeNamed, rootTypeNamed, } from '@lde/search/adapter';
5
5
  const factory = new AstFactory();
6
6
  const parser = new Parser();
7
7
  const generator = new Generator();
@@ -26,6 +26,15 @@ const generator = new Generator();
26
26
  * nested template (`{ ?root <…/ref> ?r . ?r <…/field> ?v }`), recursing into
27
27
  * the reference type to the schema’s declared depth. The referent-binding hop
28
28
  * uses the source `path`; the emitted triples use the minted aliases.
29
+ * - **`local` lookups** ({@link ReferenceStrategy.local}): a reference that also
30
+ * stores what this document states about its referent gets its branch
31
+ * extended with the **target Root Type’s own** fields, read off the referent
32
+ * and emitted under that type’s aliases – the same recursion an inline
33
+ * reference makes, through a Root Type rather than a Reference Type, and the
34
+ * same reach {@link inlineFramingDepth} already frames for. `OPTIONAL`, so a
35
+ * referent the document states nothing about keeps its row and still stores
36
+ * its id. It subsumes the key hop below, the key field being one of the
37
+ * target’s own.
29
38
  * - **references into a keyed type**: a reference naming a target that declares
30
39
  * a `key` gets its branch extended with an `OPTIONAL` hop reading the
31
40
  * referent’s key field, emitted under the **target’s** alias for it – so the
@@ -40,7 +49,7 @@ const generator = new Generator();
40
49
  export function extractionQuery(searchType, schema, options = {}) {
41
50
  const subjectVariable = options.subjectVariable ?? 'root';
42
51
  const subject = factory.termVariable(subjectVariable, factory.gen());
43
- const built = buildFor(searchType, subject, schema, { next: 0 });
52
+ const built = buildFor(searchType, subject, schema, { next: 0 }, new Set());
44
53
  if (built.branches.length === 0) {
45
54
  throw new Error(`Cannot generate an extraction CONSTRUCT for “${searchType.name}”: it declares no path-bearing field, so there is nothing to extract.`);
46
55
  }
@@ -54,14 +63,22 @@ export function extractionQueryString(searchType, schema, options) {
54
63
  /**
55
64
  * Walk a type’s path-bearing fields off a subject variable, collecting the
56
65
  * template triples (minted aliases) and one WHERE branch per field (source
57
- * paths). Recurses into an inline reference’s type off a fresh referent
58
- * variable, so the nested template keeps the `subject → referent → value` link
59
- * in one CONSTRUCT. The recursion terminates because `searchSchema` rejects
60
- * inline reference cycles.
66
+ * paths). Recurses off a fresh referent variable for the two references that
67
+ * store a referent’s fields rather than only its id – an inline reference,
68
+ * through its Reference Type, and a {@link ReferenceStrategy.local} lookup,
69
+ * through its target Root Type – so the nested template keeps the
70
+ * `subject → referent → value` link in one CONSTRUCT.
71
+ *
72
+ * `visiting` is what terminates the second one. `searchSchema` rejects inline
73
+ * reference cycles, but nothing forbids two Root Types whose `local` lookups
74
+ * point at each other, so a type already on this path contributes no further
75
+ * hop – the same cut {@link inlineFramingDepth} makes, so the depth framed and
76
+ * the depth extracted stay the same number.
61
77
  */
62
- function buildFor(searchType, subject, schema, counter) {
78
+ function buildFor(searchType, subject, schema, counter, visiting) {
63
79
  const template = [];
64
80
  const branches = [];
81
+ const onPath = new Set(visiting).add(searchType.name);
65
82
  for (const field of searchType.fields) {
66
83
  if (field.path === undefined) {
67
84
  continue;
@@ -75,7 +92,7 @@ function buildFor(searchType, subject, schema, counter) {
75
92
  }
76
93
  const referent = factory.termVariable(`r${counter.next++}`, factory.gen());
77
94
  template.push(factory.triple(subject, alias, referent));
78
- const nested = buildFor(referenceType, referent, schema, counter);
95
+ const nested = buildFor(referenceType, referent, schema, counter, onPath);
79
96
  template.push(...nested.template);
80
97
  const patterns = [
81
98
  factory.patternBgp([factory.triple(subject, sourcePath, referent)], factory.gen()),
@@ -93,17 +110,42 @@ function buildFor(searchType, subject, schema, counter) {
93
110
  const patterns = [
94
111
  factory.patternBgp([factory.triple(subject, sourcePath, value)], factory.gen()),
95
112
  ];
96
- const keyed = keyedTargetOf(field, schema);
97
- if (keyed !== undefined) {
98
- const built = buildKeyHop(keyed.target, keyed.keyField, value, counter);
99
- template.push(built.triple);
100
- patterns.push(built.pattern);
113
+ const local = localTargetOf(field, schema, onPath);
114
+ if (local === undefined) {
115
+ // Only where no local expansion follows: a `local` lookup reads the
116
+ // target’s every path-bearing field, and a keyed target’s key field is
117
+ // one of them, so emitting the hop as well would state it twice.
118
+ const keyed = keyedTargetOf(field, schema);
119
+ if (keyed !== undefined) {
120
+ const built = buildKeyHop(keyed.target, keyed.keyField, value, counter);
121
+ template.push(built.triple);
122
+ patterns.push(built.pattern);
123
+ }
124
+ }
125
+ else {
126
+ const nested = buildFor(local, value, schema, counter, onPath);
127
+ template.push(...nested.template);
128
+ // `OPTIONAL`, unlike an inline reference’s conjoined nesting: this
129
+ // referent is stored by id whether or not the referring document says
130
+ // anything about it, and conjoining would drop both together.
131
+ if (nested.branches.length > 0) {
132
+ patterns.push(factory.patternOptional([factory.patternUnion(nested.branches, factory.gen())], factory.gen()));
133
+ }
101
134
  }
102
135
  branches.push(factory.patternGroup(patterns, factory.gen()));
103
136
  }
104
137
  }
105
138
  return { template, branches };
106
139
  }
140
+ /**
141
+ * The Root Type a {@link ReferenceStrategy.local} lookup expands into here, or
142
+ * `undefined` where the field declares none – or where that type is already on
143
+ * this path, which is where the recursion stops.
144
+ */
145
+ function localTargetOf(field, schema, visiting) {
146
+ const local = localLookupTypeOf(field, schema);
147
+ return local === undefined || visiting.has(local.name) ? undefined : local;
148
+ }
107
149
  /**
108
150
  * The keyed Root Type a reference points at, with the field its key is read
109
151
  * from: a `lookup`’s `target` or an `idOnly`’s `labelSource`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lde/search-pipeline",
3
- "version": "0.25.0",
3
+ "version": "0.26.1",
4
4
  "description": "Search indexing as an @lde/pipeline instance: a Writer<Quad> that applies the @lde/search projection to extracted RDF and fans each document out to the transactional engine writer for its type's collection. Glue between the pipeline quad world and the search-engine document world; owns no projection or engine logic itself.",
5
5
  "repository": {
6
6
  "url": "git+https://github.com/ldelements/lde.git",
@@ -25,14 +25,14 @@
25
25
  "!**/*.tsbuildinfo"
26
26
  ],
27
27
  "dependencies": {
28
- "@lde/search": "^0.24.0",
28
+ "@lde/search": "^0.24.1",
29
29
  "@traqula/generator-sparql-1-1": "^1.2.0",
30
30
  "@traqula/parser-sparql-1-1": "^1.2.0",
31
31
  "@traqula/rules-sparql-1-1": "^1.2.0",
32
32
  "tslib": "^2.3.0"
33
33
  },
34
34
  "devDependencies": {
35
- "@lde/search-typesense": "^0.28.0",
35
+ "@lde/search-typesense": "^0.29.1",
36
36
  "@rdfjs/types": "^2.0.1",
37
37
  "n3": "^2.4.0",
38
38
  "testcontainers": "^12.1.0",