@lde/search-pipeline 0.18.0 → 0.18.2

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.
@@ -1,5 +1,5 @@
1
1
  import type { Dataset } from '@lde/dataset';
2
- import { Pipeline, type DatasetSelector, type DistributionResolver, type ProgressReporter, type ProvenanceStore, type Writer } from '@lde/pipeline';
2
+ import { Pipeline, type DatasetSelector, type DistributionResolver, type ProgressReporter, type ProvenanceStore, type QuadTransform, type ReaderContext, type Writer } from '@lde/pipeline';
3
3
  import type { RootType, SearchDocument, SearchSchema } from '@lde/search';
4
4
  import type { TypedSearchDocument } from './typed-search-document.js';
5
5
  /** Options for {@link searchIndexerPipeline}. */
@@ -54,6 +54,21 @@ export interface SearchIndexerPipelineOptions {
54
54
  /** {@link SearchType.name}s of the root types to source from it. */
55
55
  names: readonly string[];
56
56
  };
57
+ /**
58
+ * {@link QuadTransform}(s) attached to a root type’s reader, keyed by
59
+ * {@link SearchType.name} – the extension point a deployment reaches for
60
+ * when the data needs correcting, enriching or pruning before projection.
61
+ *
62
+ * This is what keeps a deployment with one bespoke transform on this
63
+ * convenience instead of hand-composing a {@link Pipeline}: composing by hand
64
+ * to reach the reader means restating dataset selection, registry sourcing,
65
+ * provenance and reporting, and silently losing whichever is forgotten.
66
+ *
67
+ * Named by `name` rather than by class IRI, like {@link registryTypes}; a
68
+ * name the schema does not declare throws at wiring time rather than
69
+ * attaching nothing and shipping an unenriched collection.
70
+ */
71
+ transforms?: Readonly<Record<string, QuadTransform<ReaderContext> | QuadTransform<ReaderContext>[]>>;
57
72
  /**
58
73
  * Optional per-dataset processing memory: skip a dataset whose source
59
74
  * fingerprint and {@link pipelineVersion} both match the stored record.
@@ -99,11 +114,15 @@ export interface SearchIndexerPipelineOptions {
99
114
  * await pipeline.run();
100
115
  * ```
101
116
  *
117
+ * Domain behaviour on top of the standard wiring is {@link transforms}, so
118
+ * adding it costs the behaviour rather than this whole composition.
119
+ *
102
120
  * A deployment that needs more – a bespoke root selector (the entry point is a
103
121
  * domain fact, not a class), per-stage tuning (`batchSize`, `maxConcurrency`),
104
122
  * non-SPARQL readers, or quad-level plugins – composes {@link searchStages},
105
- * {@link searchIndexWriter} and {@link Pipeline} directly; this convenience
106
- * owns no capability of its own.
123
+ * {@link searchIndexWriter} and {@link Pipeline} directly, restating the
124
+ * registry sourcing, provenance and reporting this function wires; beyond
125
+ * those, this convenience owns no capability of its own.
107
126
  */
108
127
  export declare function searchIndexerPipeline(options: SearchIndexerPipelineOptions): Pipeline<TypedSearchDocument>;
109
128
  //# sourceMappingURL=search-indexer-pipeline.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"search-indexer-pipeline.d.ts","sourceRoot":"","sources":["../src/search-indexer-pipeline.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,EAEL,QAAQ,EACR,KAAK,eAAe,EACpB,KAAK,oBAAoB,EACzB,KAAK,gBAAgB,EACrB,KAAK,eAAe,EACpB,KAAK,MAAM,EACZ,MAAM,eAAe,CAAC;AACvB,OAAO,KAAK,EAAE,QAAQ,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAO1E,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AAEtE,iDAAiD;AACjD,MAAM,WAAW,4BAA4B;IAC3C;;;;OAIG;IACH,MAAM,EAAE,YAAY,CAAC;IACrB;;;;OAIG;IACH,QAAQ,EAAE,OAAO,EAAE,GAAG,eAAe,CAAC;IACtC;;;;;;;;OAQG;IACH,oBAAoB,CAAC,EAAE,oBAAoB,CAAC;IAC5C;;;;;;;OAOG;IACH,SAAS,EAAE,CACT,UAAU,EAAE,QAAQ,EACpB,MAAM,EAAE,YAAY,KACjB,MAAM,CAAC,cAAc,CAAC,CAAC;IAC5B;;;;;;;;;;;;OAYG;IACH,aAAa,CAAC,EAAE;QACd,+DAA+D;QAC/D,QAAQ,EAAE,GAAG,CAAC;QACd,oEAAoE;QACpE,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;KAC1B,CAAC;IACF;;;;OAIG;IACH,eAAe,CAAC,EAAE,eAAe,CAAC;IAClC;;;;OAIG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB;;;OAGG;IACH,QAAQ,CAAC,EAAE,gBAAgB,GAAG,SAAS,gBAAgB,EAAE,CAAC;CAC3D;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,4BAA4B,GACpC,QAAQ,CAAC,mBAAmB,CAAC,CA0B/B"}
1
+ {"version":3,"file":"search-indexer-pipeline.d.ts","sourceRoot":"","sources":["../src/search-indexer-pipeline.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,EAEL,QAAQ,EACR,KAAK,eAAe,EACpB,KAAK,oBAAoB,EACzB,KAAK,gBAAgB,EACrB,KAAK,eAAe,EACpB,KAAK,aAAa,EAClB,KAAK,aAAa,EAClB,KAAK,MAAM,EACZ,MAAM,eAAe,CAAC;AACvB,OAAO,KAAK,EAAE,QAAQ,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAO1E,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AAEtE,iDAAiD;AACjD,MAAM,WAAW,4BAA4B;IAC3C;;;;OAIG;IACH,MAAM,EAAE,YAAY,CAAC;IACrB;;;;OAIG;IACH,QAAQ,EAAE,OAAO,EAAE,GAAG,eAAe,CAAC;IACtC;;;;;;;;OAQG;IACH,oBAAoB,CAAC,EAAE,oBAAoB,CAAC;IAC5C;;;;;;;OAOG;IACH,SAAS,EAAE,CACT,UAAU,EAAE,QAAQ,EACpB,MAAM,EAAE,YAAY,KACjB,MAAM,CAAC,cAAc,CAAC,CAAC;IAC5B;;;;;;;;;;;;OAYG;IACH,aAAa,CAAC,EAAE;QACd,+DAA+D;QAC/D,QAAQ,EAAE,GAAG,CAAC;QACd,oEAAoE;QACpE,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;KAC1B,CAAC;IACF;;;;;;;;;;;;;OAaG;IACH,UAAU,CAAC,EAAE,QAAQ,CACnB,MAAM,CACJ,MAAM,EACN,aAAa,CAAC,aAAa,CAAC,GAAG,aAAa,CAAC,aAAa,CAAC,EAAE,CAC9D,CACF,CAAC;IACF;;;;OAIG;IACH,eAAe,CAAC,EAAE,eAAe,CAAC;IAClC;;;;OAIG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB;;;OAGG;IACH,QAAQ,CAAC,EAAE,gBAAgB,GAAG,SAAS,gBAAgB,EAAE,CAAC;CAC3D;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,4BAA4B,GACpC,QAAQ,CAAC,mBAAmB,CAAC,CA4B/B"}
@@ -28,15 +28,20 @@ import { registrySource, searchStages, selectByClass, } from './search-stages.js
28
28
  * await pipeline.run();
29
29
  * ```
30
30
  *
31
+ * Domain behaviour on top of the standard wiring is {@link transforms}, so
32
+ * adding it costs the behaviour rather than this whole composition.
33
+ *
31
34
  * A deployment that needs more – a bespoke root selector (the entry point is a
32
35
  * domain fact, not a class), per-stage tuning (`batchSize`, `maxConcurrency`),
33
36
  * non-SPARQL readers, or quad-level plugins – composes {@link searchStages},
34
- * {@link searchIndexWriter} and {@link Pipeline} directly; this convenience
35
- * owns no capability of its own.
37
+ * {@link searchIndexWriter} and {@link Pipeline} directly, restating the
38
+ * registry sourcing, provenance and reporting this function wires; beyond
39
+ * those, this convenience owns no capability of its own.
36
40
  */
37
41
  export function searchIndexerPipeline(options) {
38
- const { schema, datasets, registryTypes } = options;
42
+ const { schema, datasets, registryTypes, transforms } = options;
39
43
  const fromRegistry = registryTypeNames(schema, registryTypes?.names);
44
+ assertDeclaredTypeNames(schema, Object.keys(transforms ?? {}), 'transform');
40
45
  const sourceFor = registryTypes === undefined
41
46
  ? undefined
42
47
  : registrySource(registryTypes.endpoint);
@@ -52,6 +57,7 @@ export function searchIndexerPipeline(options) {
52
57
  rootVariable: 'root',
53
58
  itemSelector: selectByClass(searchType),
54
59
  sourceFor: fromRegistry.has(searchType.name) ? sourceFor : undefined,
60
+ transform: transforms?.[searchType.name],
55
61
  })),
56
62
  }),
57
63
  writers: searchIndexWriter({ schema, writerFor: options.writerFor }),
@@ -68,14 +74,23 @@ export function searchIndexerPipeline(options) {
68
74
  */
69
75
  function registryTypeNames(schema, declaredNames) {
70
76
  const names = new Set(declaredNames ?? []);
77
+ assertDeclaredTypeNames(schema, [...names], 'registry root');
78
+ return names;
79
+ }
80
+ /**
81
+ * Every name must be one the schema declares. Shared by the options that key
82
+ * behaviour by {@link SearchType.name}: naming a type the schema does not have
83
+ * is a configuration mistake whose only other symptom is an option that
84
+ * silently does nothing.
85
+ */
86
+ function assertDeclaredTypeNames(schema, names, label) {
71
87
  const declared = new Set([...schema.values()].map((searchType) => searchType.name));
72
- const unknown = [...names].filter((name) => !declared.has(name));
88
+ const unknown = names.filter((name) => !declared.has(name));
73
89
  if (unknown.length > 0) {
74
- throw new Error(`Unknown registry root type(s) ${unknown
90
+ throw new Error(`Unknown ${label} type(s) ${unknown
75
91
  .map((name) => `“${name}”`)
76
92
  .join(', ')}; the schema declares ${[...declared]
77
93
  .map((name) => `“${name}”`)
78
94
  .join(', ')}.`);
79
95
  }
80
- return names;
81
96
  }
@@ -1,4 +1,4 @@
1
- import { Stage, type ItemSelector, type StageOptions, type StageReaders } from '@lde/pipeline';
1
+ import { Stage, type ItemSelector, type QuadTransform, type ReaderContext, type StageOptions, type StageReaders } from '@lde/pipeline';
2
2
  import { type RootType, type SearchSchema } from '@lde/search';
3
3
  import type { TypedSearchDocument } from './typed-search-document.js';
4
4
  /** One root type’s stage in a search pipeline. */
@@ -30,10 +30,27 @@ export interface SearchStageType {
30
30
  * from `searchType` ({@link extractionQuery}), with `rootVariable` as the free
31
31
  * subject the batch’s roots bind to – the schema-derived reader the projection
32
32
  * is guaranteed to agree with (they share the {@link irAlias} convention).
33
- * Supply your own only to read from a non-SPARQL source, merge several
34
- * readers, or attach transforms.
33
+ * Supply your own only to read from a non-SPARQL source or merge several
34
+ * readers; to enrich the default reader’s quads, use {@link transform}.
35
35
  */
36
36
  readers?: StageReaders;
37
+ /**
38
+ * {@link QuadTransform}(s) attached to the **default** reader – the usual way
39
+ * to add behaviour to this stage: correct the data, mint a quad the source
40
+ * does not ship, drop one it should not.
41
+ *
42
+ * Attaching here rather than building the reader yourself is what keeps the
43
+ * reader’s `subjectVariable` and this stage’s {@link rootVariable} in
44
+ * agreement: nothing cross-checks them, and a mismatch extracts nothing.
45
+ * Mutually exclusive with {@link readers} – a caller supplying its own
46
+ * reader(s) attaches transforms to them directly ({@link AttachedReader}),
47
+ * since only that caller knows which of several readers each belongs to.
48
+ *
49
+ * A field a transform fills must still declare a `path`: projection skips a
50
+ * field with neither a `path` nor a `derive`, so a transform-minted IR Alias
51
+ * is otherwise never read.
52
+ */
53
+ transform?: QuadTransform<ReaderContext> | QuadTransform<ReaderContext>[];
37
54
  /**
38
55
  * Where this type’s stage reads, when that is not the dataset’s own
39
56
  * distribution – see {@link StageOptions.sourceFor}. {@link registrySource}
@@ -1 +1 @@
1
- {"version":3,"file":"search-stages.d.ts","sourceRoot":"","sources":["../src/search-stages.ts"],"names":[],"mappings":"AACA,OAAO,EAGL,KAAK,EACL,KAAK,YAAY,EACjB,KAAK,YAAY,EACjB,KAAK,YAAY,EAClB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAgB,KAAK,QAAQ,EAAE,KAAK,YAAY,EAAE,MAAM,aAAa,CAAC;AAE7E,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AAEtE,kDAAkD;AAClD,MAAM,WAAW,eAAe;IAC9B;;;;;;OAMG;IACH,UAAU,EAAE,QAAQ,CAAC;IACrB;;;;;OAKG;IACH,YAAY,EAAE,MAAM,CAAC;IACrB;;;;OAIG;IACH,YAAY,EAAE,YAAY,CAAC;IAC3B;;;;;;;;OAQG;IACH,OAAO,CAAC,EAAE,YAAY,CAAC;IACvB;;;;;OAKG;IACH,SAAS,CAAC,EAAE,YAAY,CAAC,mBAAmB,CAAC,CAAC,WAAW,CAAC,CAAC;IAC3D;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,8EAA8E;IAC9E,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;;OAIG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,wCAAwC;AACxC,MAAM,WAAW,mBAAmB;IAClC;;;OAGG;IACH,MAAM,EAAE,YAAY,CAAC;IACrB,4DAA4D;IAC5D,KAAK,EAAE,SAAS,eAAe,EAAE,CAAC;CACnC;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,YAAY,CAC1B,OAAO,EAAE,mBAAmB,GAC3B,KAAK,CAAC,mBAAmB,CAAC,EAAE,CAiE9B;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,aAAa,CAC3B,UAAU,EAAE,QAAQ,EACpB,YAAY,SAAS,GACpB,YAAY,CAId;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,wBAAgB,cAAc,CAC5B,QAAQ,EAAE,GAAG,GACZ,WAAW,CAAC,eAAe,CAAC,WAAW,CAAC,CAAC,CAE3C"}
1
+ {"version":3,"file":"search-stages.d.ts","sourceRoot":"","sources":["../src/search-stages.ts"],"names":[],"mappings":"AACA,OAAO,EAGL,KAAK,EAEL,KAAK,YAAY,EACjB,KAAK,aAAa,EAClB,KAAK,aAAa,EAClB,KAAK,YAAY,EACjB,KAAK,YAAY,EAClB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAgB,KAAK,QAAQ,EAAE,KAAK,YAAY,EAAE,MAAM,aAAa,CAAC;AAE7E,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AAEtE,kDAAkD;AAClD,MAAM,WAAW,eAAe;IAC9B;;;;;;OAMG;IACH,UAAU,EAAE,QAAQ,CAAC;IACrB;;;;;OAKG;IACH,YAAY,EAAE,MAAM,CAAC;IACrB;;;;OAIG;IACH,YAAY,EAAE,YAAY,CAAC;IAC3B;;;;;;;;OAQG;IACH,OAAO,CAAC,EAAE,YAAY,CAAC;IACvB;;;;;;;;;;;;;;;OAeG;IACH,SAAS,CAAC,EAAE,aAAa,CAAC,aAAa,CAAC,GAAG,aAAa,CAAC,aAAa,CAAC,EAAE,CAAC;IAC1E;;;;;OAKG;IACH,SAAS,CAAC,EAAE,YAAY,CAAC,mBAAmB,CAAC,CAAC,WAAW,CAAC,CAAC;IAC3D;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,8EAA8E;IAC9E,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;;OAIG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,wCAAwC;AACxC,MAAM,WAAW,mBAAmB;IAClC;;;OAGG;IACH,MAAM,EAAE,YAAY,CAAC;IACrB,4DAA4D;IAC5D,KAAK,EAAE,SAAS,eAAe,EAAE,CAAC;CACnC;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,YAAY,CAC1B,OAAO,EAAE,mBAAmB,GAC3B,KAAK,CAAC,mBAAmB,CAAC,EAAE,CA6E9B;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,aAAa,CAC3B,UAAU,EAAE,QAAQ,EACpB,YAAY,SAAS,GACpB,YAAY,CAId;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,wBAAgB,cAAc,CAC5B,QAAQ,EAAE,GAAG,GACZ,WAAW,CAAC,eAAe,CAAC,WAAW,CAAC,CAAC,CAE3C"}
@@ -27,15 +27,25 @@ export function searchStages(options) {
27
27
  throw new Error(`Search type “${type.searchType.name}” (class ${type.searchType.class}) is not in the schema; searchStages projects only types the schema declares.`);
28
28
  }
29
29
  const { rootVariable } = type;
30
+ if (type.readers !== undefined && type.transform !== undefined) {
31
+ // Which of the caller’s readers the transform belongs to is knowable only
32
+ // to that caller, so guessing (all of them? the first?) would be a silent
33
+ // wrong answer. Attach it as an AttachedReader instead.
34
+ throw new Error(`Search type “${searchType.name}”: “transform” attaches to the default reader, so it cannot be combined with “readers” – attach the transform to your own reader instead ({ reader, transform }).`);
35
+ }
30
36
  // Default to the Extraction CONSTRUCT generated from the schema, its subject
31
37
  // left free for the batch’s VALUES injection. The reader and the projection
32
- // then agree by construction: both key off the same IR Aliases.
38
+ // then agree by construction: both key off the same IR Aliases – which is
39
+ // also why an attached transform never has to restate the subject variable.
33
40
  const readers = type.readers ??
34
- new SparqlConstructReader({
35
- query: extractionQueryString(searchType, schema, {
36
- subjectVariable: rootVariable,
41
+ {
42
+ reader: new SparqlConstructReader({
43
+ query: extractionQueryString(searchType, schema, {
44
+ subjectVariable: rootVariable,
45
+ }),
37
46
  }),
38
- });
47
+ transform: type.transform,
48
+ };
39
49
  return new Stage({
40
50
  name: searchType.name,
41
51
  readers,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lde/search-pipeline",
3
- "version": "0.18.0",
3
+ "version": "0.18.2",
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.18.0",
28
+ "@lde/search": "^0.18.1",
29
29
  "@traqula/generator-sparql-1-1": "^1.1.8",
30
30
  "@traqula/parser-sparql-1-1": "^1.1.5",
31
31
  "@traqula/rules-sparql-1-1": "^1.1.0",
32
32
  "tslib": "^2.3.0"
33
33
  },
34
34
  "devDependencies": {
35
- "@lde/search-typesense": "^0.21.0",
35
+ "@lde/search-typesense": "^0.21.1",
36
36
  "@rdfjs/types": "^2.0.1",
37
37
  "n3": "^2.1.2",
38
38
  "testcontainers": "^12.0.3",