@lde/search-pipeline 0.30.0 → 0.31.0
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/extraction.d.ts +6 -0
- package/dist/extraction.d.ts.map +1 -1
- package/dist/extraction.js +79 -50
- package/package.json +3 -3
package/dist/extraction.d.ts
CHANGED
|
@@ -49,6 +49,12 @@ export interface ExtractionOptions {
|
|
|
49
49
|
* `OPTIONAL`, so a referent with no key candidate keeps its row. The root side
|
|
50
50
|
* needs nothing: a key field is a declared field, so its own branch and
|
|
51
51
|
* template triple are already there.
|
|
52
|
+
* - **references naming several targets**: the referent may be of any of them,
|
|
53
|
+
* so its branch also reads the referent’s `rdf:type` (`OPTIONAL`, emitted
|
|
54
|
+
* as is – framing carries it as `@type`), which is what the projection
|
|
55
|
+
* matches against each target’s `class` to tell which one it is. Every
|
|
56
|
+
* target then contributes its own expansion or key hop, so whichever the
|
|
57
|
+
* referent turns out to be, its declaration is in the frame.
|
|
52
58
|
*
|
|
53
59
|
* Wire the result into a `SparqlConstructReader` (see `searchStages`), which
|
|
54
60
|
* runs it per batch with the roots injected as VALUES.
|
package/dist/extraction.d.ts.map
CHANGED
|
@@ -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;
|
|
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,EAGV,YAAY,EACZ,UAAU,EACX,MAAM,aAAa,CAAC;AAQrB,2CAA2C;AAC3C,MAAM,WAAW,iBAAiB;IAChC;;;;;;;OAOG;IACH,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAC;CACnC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8CG;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"}
|
package/dist/extraction.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
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,
|
|
4
|
+
import { fieldNamed, irAlias, isInlineReference, localLookupTargetsOf, referencedTargetsOf, referenceTypeNamed, } from '@lde/search/adapter';
|
|
5
5
|
const factory = new AstFactory();
|
|
6
6
|
const parser = new Parser();
|
|
7
7
|
const generator = new Generator();
|
|
8
|
+
const RDF_TYPE = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type';
|
|
8
9
|
/**
|
|
9
10
|
* Generate a type’s **Extraction** CONSTRUCT from its {@link SearchType}
|
|
10
11
|
* declaration: the query whose output the projection frames and reads. Pure –
|
|
@@ -42,6 +43,12 @@ const generator = new Generator();
|
|
|
42
43
|
* `OPTIONAL`, so a referent with no key candidate keeps its row. The root side
|
|
43
44
|
* needs nothing: a key field is a declared field, so its own branch and
|
|
44
45
|
* template triple are already there.
|
|
46
|
+
* - **references naming several targets**: the referent may be of any of them,
|
|
47
|
+
* so its branch also reads the referent’s `rdf:type` (`OPTIONAL`, emitted
|
|
48
|
+
* as is – framing carries it as `@type`), which is what the projection
|
|
49
|
+
* matches against each target’s `class` to tell which one it is. Every
|
|
50
|
+
* target then contributes its own expansion or key hop, so whichever the
|
|
51
|
+
* referent turns out to be, its declaration is in the frame.
|
|
45
52
|
*
|
|
46
53
|
* Wire the result into a `SparqlConstructReader` (see `searchStages`), which
|
|
47
54
|
* runs it per batch with the roots injected as VALUES.
|
|
@@ -110,27 +117,42 @@ function buildFor(searchType, subject, schema, counter, visiting) {
|
|
|
110
117
|
const patterns = [
|
|
111
118
|
factory.patternBgp([factory.triple(subject, sourcePath, value)], factory.gen()),
|
|
112
119
|
];
|
|
113
|
-
const
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
patterns.push(built.pattern);
|
|
123
|
-
}
|
|
120
|
+
const targets = referencedTargetsOf(field, schema);
|
|
121
|
+
// Every hop off the referent – its type, each key, each expansion – is
|
|
122
|
+
// its own branch of ONE optional union: each binds only its own
|
|
123
|
+
// variables, so a multi-valued type or key multiplies against nothing.
|
|
124
|
+
const expansions = [];
|
|
125
|
+
if (targets.length > 1) {
|
|
126
|
+
const built = buildTypeHop(value, counter);
|
|
127
|
+
template.push(built.triple);
|
|
128
|
+
expansions.push(built.pattern);
|
|
124
129
|
}
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
130
|
+
const local = localTargetsOf(field, schema, onPath);
|
|
131
|
+
for (const target of targets) {
|
|
132
|
+
if (!local.includes(target)) {
|
|
133
|
+
// Only where no local expansion follows: a `local` lookup reads the
|
|
134
|
+
// target’s every path-bearing field, and a keyed target’s key field
|
|
135
|
+
// is one of them, so emitting the hop as well would state it twice.
|
|
136
|
+
const keyed = keyedFieldOf(target);
|
|
137
|
+
if (keyed !== undefined) {
|
|
138
|
+
const built = buildKeyHop(target, keyed, value, counter);
|
|
139
|
+
template.push(built.triple);
|
|
140
|
+
expansions.push(built.pattern);
|
|
141
|
+
}
|
|
142
|
+
continue;
|
|
133
143
|
}
|
|
144
|
+
const nested = buildFor(target, value, schema, counter, onPath);
|
|
145
|
+
template.push(...nested.template);
|
|
146
|
+
expansions.push(...nested.branches);
|
|
147
|
+
}
|
|
148
|
+
// `OPTIONAL`, unlike an inline reference’s conjoined nesting: this
|
|
149
|
+
// referent is stored by id whether or not the referring document says
|
|
150
|
+
// anything about it, and conjoining would drop both together. Several
|
|
151
|
+
// targets’ expansions share the one UNION: a referent is of one kind,
|
|
152
|
+
// and the branches of the others simply bind nothing for it. A
|
|
153
|
+
// single-branch union serialises to just its group.
|
|
154
|
+
if (expansions.length > 0) {
|
|
155
|
+
patterns.push(factory.patternOptional([factory.patternUnion(expansions, factory.gen())], factory.gen()));
|
|
134
156
|
}
|
|
135
157
|
branches.push(factory.patternGroup(patterns, factory.gen()));
|
|
136
158
|
}
|
|
@@ -138,53 +160,60 @@ function buildFor(searchType, subject, schema, counter, visiting) {
|
|
|
138
160
|
return { template, branches };
|
|
139
161
|
}
|
|
140
162
|
/**
|
|
141
|
-
* The Root
|
|
142
|
-
*
|
|
143
|
-
*
|
|
163
|
+
* The Root Types a {@link ReferenceStrategy.local} lookup expands into here:
|
|
164
|
+
* none where the field declares none, and never one already on this path,
|
|
165
|
+
* which is where the recursion stops.
|
|
144
166
|
*/
|
|
145
|
-
function
|
|
146
|
-
|
|
147
|
-
return local === undefined || visiting.has(local.name) ? undefined : local;
|
|
167
|
+
function localTargetsOf(field, schema, visiting) {
|
|
168
|
+
return localLookupTargetsOf(field, schema).filter((target) => !visiting.has(target.name));
|
|
148
169
|
}
|
|
149
170
|
/**
|
|
150
|
-
* The
|
|
151
|
-
*
|
|
152
|
-
* ({@link
|
|
153
|
-
*
|
|
154
|
-
*
|
|
155
|
-
* rather than a rule of its own: a reference that names no target keeps the
|
|
156
|
-
* node IRI, and needs no hop.
|
|
171
|
+
* The field a Root Type’s key is read from, when it declares a
|
|
172
|
+
* {@link RootType.key}. Naming the target is exactly the boundary the
|
|
173
|
+
* projection re-keys along ({@link referencedTargetsOf}), so the extraction
|
|
174
|
+
* reads the same declarations rather than a rule of its own: a reference that
|
|
175
|
+
* names no target keeps the node IRI, and needs no hop.
|
|
157
176
|
*/
|
|
158
|
-
function
|
|
159
|
-
if (
|
|
160
|
-
return undefined;
|
|
161
|
-
}
|
|
162
|
-
const targetName = labelSourceNameOf(field);
|
|
163
|
-
const target = targetName === undefined ? undefined : rootTypeNamed(schema, targetName);
|
|
164
|
-
if (target?.key === undefined) {
|
|
177
|
+
function keyedFieldOf(target) {
|
|
178
|
+
if (target.key === undefined) {
|
|
165
179
|
return undefined;
|
|
166
180
|
}
|
|
167
181
|
// `searchSchema` guarantees a declared, path-bearing key field, so the target
|
|
168
182
|
// – which came out of the schema – always has one.
|
|
169
|
-
|
|
170
|
-
|
|
183
|
+
return fieldNamed(target, target.key.field);
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* The one-triple hop that reads a referent’s `rdf:type`, for a reference whose
|
|
187
|
+
* referent may be of several kinds: emitted under `rdf:type` itself, which
|
|
188
|
+
* framing turns into the node’s `@type`. The caller binds it inside the
|
|
189
|
+
* reference’s `OPTIONAL` union, so an untyped referent keeps its row and falls
|
|
190
|
+
* back to the first target declared.
|
|
191
|
+
*/
|
|
192
|
+
function buildTypeHop(referent, counter) {
|
|
193
|
+
const type = factory.termVariable(`t${counter.next++}`, factory.gen());
|
|
194
|
+
const predicate = factory.termNamed(factory.gen(), RDF_TYPE);
|
|
195
|
+
return {
|
|
196
|
+
triple: factory.triple(referent, predicate, type),
|
|
197
|
+
pattern: factory.patternGroup([
|
|
198
|
+
factory.patternBgp([factory.triple(referent, predicate, type)], factory.gen()),
|
|
199
|
+
], factory.gen()),
|
|
200
|
+
};
|
|
171
201
|
}
|
|
172
202
|
/**
|
|
173
203
|
* The one-field hop that reads a referent’s key: a template triple emitting the
|
|
174
|
-
* key field under the **target’s** IR Alias, and
|
|
175
|
-
*
|
|
176
|
-
*
|
|
177
|
-
* key candidate keeps its row and still stores
|
|
178
|
-
* whole reference dropping out of the CONSTRUCT.
|
|
204
|
+
* key field under the **target’s** IR Alias, and a branch binding it. The
|
|
205
|
+
* referent recursion an inline reference already performs, for a single field –
|
|
206
|
+
* bound by the caller inside the reference’s `OPTIONAL` union rather than
|
|
207
|
+
* conjoined, so a referent with no key candidate keeps its row and still stores
|
|
208
|
+
* its own IRI, instead of the whole reference dropping out of the CONSTRUCT.
|
|
179
209
|
*
|
|
180
|
-
* It
|
|
181
|
-
* multiplies against nothing.
|
|
210
|
+
* It is a branch of its own in that union, so it multiplies against nothing.
|
|
182
211
|
*/
|
|
183
212
|
function buildKeyHop(target, keyField, referent, counter) {
|
|
184
213
|
const key = factory.termVariable(`k${counter.next++}`, factory.gen());
|
|
185
214
|
return {
|
|
186
215
|
triple: factory.triple(referent, factory.termNamed(factory.gen(), irAlias(target, keyField)), key),
|
|
187
|
-
pattern: factory.
|
|
216
|
+
pattern: factory.patternGroup([
|
|
188
217
|
factory.patternBgp([factory.triple(referent, liftPath(keyField.path), key)], factory.gen()),
|
|
189
218
|
], factory.gen()),
|
|
190
219
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lde/search-pipeline",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.31.0",
|
|
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.
|
|
28
|
+
"@lde/search": "^0.29.0",
|
|
29
29
|
"@traqula/generator-sparql-1-1": "^1.3.1",
|
|
30
30
|
"@traqula/parser-sparql-1-1": "^1.3.1",
|
|
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.
|
|
35
|
+
"@lde/search-typesense": "^0.34.0",
|
|
36
36
|
"@rdfjs/types": "^2.0.1",
|
|
37
37
|
"n3": "^2.7.12",
|
|
38
38
|
"testcontainers": "^12.1.0",
|