@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 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.
@@ -0,0 +1,3 @@
1
+ export { searchIndexWriter } from './search-index-writer.js';
2
+ export type { SearchIndexWriterOptions } from './search-index-writer.js';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -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
+ }