@lde/search-pipeline 0.1.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/README.md +79 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1 -0
- package/dist/search-index-writer.d.ts +35 -0
- package/dist/search-index-writer.d.ts.map +1 -0
- package/dist/search-index-writer.js +70 -0
- package/package.json +39 -0
package/README.md
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# @lde/search-pipeline
|
|
2
|
+
|
|
3
|
+
Search indexing as an [`@lde/pipeline`](../pipeline) Configurable Pipeline
|
|
4
|
+
instance: this package supplies the glue between the pipeline’s quad world
|
|
5
|
+
and a search engine’s document world, so indexing reuses the existing spine –
|
|
6
|
+
Discovery → Selection → Iteration → change-gate (skip-unchanged) – instead of
|
|
7
|
+
rebuilding it per consumer.
|
|
8
|
+
|
|
9
|
+
The division of labour ([ADR 6](../../docs/decisions/0006-make-the-writer-transaction-aware.md)):
|
|
10
|
+
|
|
11
|
+
- [`@lde/search`](../search) owns the engine-agnostic projection (framed
|
|
12
|
+
document + field model) and stays pipeline-free;
|
|
13
|
+
- `@lde/search-<engine>` (e.g. [`@lde/search-typesense`](../search-typesense))
|
|
14
|
+
is the engine adapter: a transactional `Writer` consuming projected
|
|
15
|
+
documents, owning the run lifecycle (Blue/green alias swap or In-place
|
|
16
|
+
sweeps, cross-pod lock);
|
|
17
|
+
- **this package** composes them: `searchIndexWriter` is the one
|
|
18
|
+
type-changing step (quad → document), shared across engines.
|
|
19
|
+
|
|
20
|
+
## Usage
|
|
21
|
+
|
|
22
|
+
```typescript
|
|
23
|
+
import { Pipeline, Stage, SparqlConstructReader } from '@lde/pipeline';
|
|
24
|
+
import { searchSchema } from '@lde/search';
|
|
25
|
+
import { BlueGreenRebuild } from '@lde/search-typesense';
|
|
26
|
+
import { searchIndexWriter } from '@lde/search-pipeline';
|
|
27
|
+
|
|
28
|
+
const schema = searchSchema({
|
|
29
|
+
name: 'dataset',
|
|
30
|
+
type: 'http://www.w3.org/ns/dcat#Dataset',
|
|
31
|
+
fields: [
|
|
32
|
+
{
|
|
33
|
+
name: 'title',
|
|
34
|
+
kind: 'text',
|
|
35
|
+
path: 'http://purl.org/dc/terms/title',
|
|
36
|
+
locales: ['und'],
|
|
37
|
+
output: true,
|
|
38
|
+
searchable: true,
|
|
39
|
+
},
|
|
40
|
+
],
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
const pipeline = new Pipeline({
|
|
44
|
+
datasetSelector,
|
|
45
|
+
stages: [
|
|
46
|
+
new Stage({
|
|
47
|
+
name: 'extract',
|
|
48
|
+
readers: new SparqlConstructReader({ query: '…' }),
|
|
49
|
+
}),
|
|
50
|
+
],
|
|
51
|
+
writers: searchIndexWriter({
|
|
52
|
+
schema,
|
|
53
|
+
writer: new BlueGreenRebuild(
|
|
54
|
+
typesenseClient,
|
|
55
|
+
schema.get('http://www.w3.org/ns/dcat#Dataset')!,
|
|
56
|
+
{
|
|
57
|
+
name: 'datasets',
|
|
58
|
+
},
|
|
59
|
+
),
|
|
60
|
+
}),
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
await pipeline.run();
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Each dataset’s extracted CONSTRUCT quads are buffered until the dataset
|
|
67
|
+
completes, then framed by root type and projected into flat search documents
|
|
68
|
+
(`@lde/search`’s `projectGraph`), and written to the engine run – before the
|
|
69
|
+
engine acts on the dataset’s completion, so an In-place stale sweep never
|
|
70
|
+
races its own documents. The run lifecycle (run context, per-dataset flush
|
|
71
|
+
outcome, commit/abort) passes through unchanged: the engine writer’s update
|
|
72
|
+
mode governs, and the pipeline never branches on it.
|
|
73
|
+
|
|
74
|
+
## Memory
|
|
75
|
+
|
|
76
|
+
Memory is bounded by one dataset’s extraction and released at each dataset
|
|
77
|
+
flush – nothing accumulates across datasets. Streaming bounded entity batches
|
|
78
|
+
_within_ one huge dataset needs the two-level iteration (dataset →
|
|
79
|
+
entity-URI batches) and is not implemented yet.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAC7D,YAAY,EAAE,wBAAwB,EAAE,MAAM,0BAA0B,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { searchIndexWriter } from './search-index-writer.js';
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { Quad } from '@rdfjs/types';
|
|
2
|
+
import type { Writer } from '@lde/pipeline';
|
|
3
|
+
import { type SearchDocument, type SearchSchema } from '@lde/search';
|
|
4
|
+
/** Options for {@link searchIndexWriter}. */
|
|
5
|
+
export interface SearchIndexWriterOptions {
|
|
6
|
+
/**
|
|
7
|
+
* The declarative schema driving the projection: one {@link SearchType} per
|
|
8
|
+
* root type, each mapping framed RDF to flat search-document fields.
|
|
9
|
+
*/
|
|
10
|
+
schema: SearchSchema;
|
|
11
|
+
/**
|
|
12
|
+
* The engine adapter the projected documents are written to – e.g.
|
|
13
|
+
* `@lde/search-typesense`’s `BlueGreenRebuild` or `InPlaceRebuild`. The
|
|
14
|
+
* run lifecycle (context, per-dataset flush outcome, commit/abort) is
|
|
15
|
+
* forwarded unchanged, so the engine writer’s update mode governs.
|
|
16
|
+
*/
|
|
17
|
+
writer: Writer<SearchDocument>;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* The projection step of a search-indexing pipeline, as a quad `Writer`: it
|
|
21
|
+
* turns each dataset’s extracted CONSTRUCT quads into engine-agnostic search
|
|
22
|
+
* documents ({@link projectGraph}: frame by root type, then project each node
|
|
23
|
+
* with its type’s declaration) and feeds them to the engine writer. This is
|
|
24
|
+
* the one type-changing step (quad → document), shared across engines –
|
|
25
|
+
* which is why it lives here and not inside an engine adapter.
|
|
26
|
+
*
|
|
27
|
+
* A dataset’s quads are buffered until its flush and projected then, so the
|
|
28
|
+
* documents land before the engine acts on the dataset’s completion (e.g. an
|
|
29
|
+
* In-place stale sweep). Memory is bounded by one dataset’s extraction, and
|
|
30
|
+
* released at each flush – nothing accumulates across datasets. Streaming
|
|
31
|
+
* bounded entity batches *within* one huge dataset needs the two-level
|
|
32
|
+
* iteration (dataset → entity-URI batches) and is not implemented yet.
|
|
33
|
+
*/
|
|
34
|
+
export declare function searchIndexWriter(options: SearchIndexWriterOptions): Writer<Quad>;
|
|
35
|
+
//# sourceMappingURL=search-index-writer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"search-index-writer.d.ts","sourceRoot":"","sources":["../src/search-index-writer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,cAAc,CAAC;AAEzC,OAAO,KAAK,EAIV,MAAM,EACP,MAAM,eAAe,CAAC;AACvB,OAAO,EAEL,KAAK,cAAc,EACnB,KAAK,YAAY,EAClB,MAAM,aAAa,CAAC;AAErB,6CAA6C;AAC7C,MAAM,WAAW,wBAAwB;IACvC;;;OAGG;IACH,MAAM,EAAE,YAAY,CAAC;IACrB;;;;;OAKG;IACH,MAAM,EAAE,MAAM,CAAC,cAAc,CAAC,CAAC;CAChC;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,wBAAwB,GAChC,MAAM,CAAC,IAAI,CAAC,CA8Dd"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { projectGraph, } from '@lde/search';
|
|
2
|
+
/**
|
|
3
|
+
* The projection step of a search-indexing pipeline, as a quad `Writer`: it
|
|
4
|
+
* turns each dataset’s extracted CONSTRUCT quads into engine-agnostic search
|
|
5
|
+
* documents ({@link projectGraph}: frame by root type, then project each node
|
|
6
|
+
* with its type’s declaration) and feeds them to the engine writer. This is
|
|
7
|
+
* the one type-changing step (quad → document), shared across engines –
|
|
8
|
+
* which is why it lives here and not inside an engine adapter.
|
|
9
|
+
*
|
|
10
|
+
* A dataset’s quads are buffered until its flush and projected then, so the
|
|
11
|
+
* documents land before the engine acts on the dataset’s completion (e.g. an
|
|
12
|
+
* In-place stale sweep). Memory is bounded by one dataset’s extraction, and
|
|
13
|
+
* released at each flush – nothing accumulates across datasets. Streaming
|
|
14
|
+
* bounded entity batches *within* one huge dataset needs the two-level
|
|
15
|
+
* iteration (dataset → entity-URI batches) and is not implemented yet.
|
|
16
|
+
*/
|
|
17
|
+
export function searchIndexWriter(options) {
|
|
18
|
+
const { schema, writer } = options;
|
|
19
|
+
return {
|
|
20
|
+
async openRun(context) {
|
|
21
|
+
const run = await writer.openRun(context);
|
|
22
|
+
// One buffered pass per dataset, keyed by IRI. The pipeline processes
|
|
23
|
+
// datasets sequentially, but keeping the key explicit makes a write
|
|
24
|
+
// after another dataset's flush safe too.
|
|
25
|
+
const passes = new Map();
|
|
26
|
+
const project = async (pass) => {
|
|
27
|
+
passes.delete(pass.dataset.iri.toString());
|
|
28
|
+
if (pass.quads.length === 0) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
await run.write(pass.dataset, projectGraph(pass.quads, schema));
|
|
32
|
+
};
|
|
33
|
+
return {
|
|
34
|
+
write: async (dataset, quads) => {
|
|
35
|
+
const key = dataset.iri.toString();
|
|
36
|
+
let pass = passes.get(key);
|
|
37
|
+
if (pass === undefined) {
|
|
38
|
+
pass = { dataset, quads: [] };
|
|
39
|
+
passes.set(key, pass);
|
|
40
|
+
}
|
|
41
|
+
for await (const quad of quads) {
|
|
42
|
+
pass.quads.push(quad);
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
flush: async (dataset, outcome) => {
|
|
46
|
+
const pass = passes.get(dataset.iri.toString());
|
|
47
|
+
if (pass !== undefined) {
|
|
48
|
+
await project(pass);
|
|
49
|
+
}
|
|
50
|
+
await run.flush?.(dataset, outcome);
|
|
51
|
+
},
|
|
52
|
+
reset: async (dataset) => {
|
|
53
|
+
// Discard the buffered pass so the re-run replaces it, and let the
|
|
54
|
+
// engine writer discard whatever it already holds for the dataset.
|
|
55
|
+
passes.delete(dataset.iri.toString());
|
|
56
|
+
await run.reset?.(dataset);
|
|
57
|
+
},
|
|
58
|
+
commit: async () => {
|
|
59
|
+
// Safety net: project passes that were never flushed, so a
|
|
60
|
+
// committed run never silently drops written quads.
|
|
61
|
+
for (const pass of [...passes.values()]) {
|
|
62
|
+
await project(pass);
|
|
63
|
+
}
|
|
64
|
+
await run.commit();
|
|
65
|
+
},
|
|
66
|
+
abort: (error) => run.abort(error),
|
|
67
|
+
};
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@lde/search-pipeline",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Search indexing as an @lde/pipeline instance: a Writer<Quad> that applies the @lde/search projection to extracted RDF and streams the documents to a transactional search-engine writer. Glue between the pipeline quad world and the search-engine document world; owns no projection or engine logic itself.",
|
|
5
|
+
"repository": {
|
|
6
|
+
"url": "git+https://github.com/ldelements/lde.git",
|
|
7
|
+
"directory": "packages/search-pipeline"
|
|
8
|
+
},
|
|
9
|
+
"license": "MIT",
|
|
10
|
+
"type": "module",
|
|
11
|
+
"exports": {
|
|
12
|
+
"./package.json": "./package.json",
|
|
13
|
+
".": {
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
|
+
"import": "./dist/index.js",
|
|
16
|
+
"development": "./src/index.ts",
|
|
17
|
+
"default": "./dist/index.js"
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"main": "./dist/index.js",
|
|
21
|
+
"module": "./dist/index.js",
|
|
22
|
+
"types": "./dist/index.d.ts",
|
|
23
|
+
"files": [
|
|
24
|
+
"dist",
|
|
25
|
+
"!**/*.tsbuildinfo"
|
|
26
|
+
],
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"@lde/search": "^0.3.1",
|
|
29
|
+
"@rdfjs/types": "^2.0.1",
|
|
30
|
+
"tslib": "^2.3.0"
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"n3": "^2.1.0"
|
|
34
|
+
},
|
|
35
|
+
"peerDependencies": {
|
|
36
|
+
"@lde/dataset": "^0.7.8",
|
|
37
|
+
"@lde/pipeline": "^0.33.3"
|
|
38
|
+
}
|
|
39
|
+
}
|