@lde/search-indexer 0.27.1 → 0.28.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/cli.js +1 -1
- package/dist/composition.d.ts +4 -1
- package/dist/composition.d.ts.map +1 -1
- package/dist/composition.js +18 -6
- package/dist/config.d.ts +7 -2
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +56 -9
- package/dist/indexer.js +1 -1
- package/package.json +8 -7
package/dist/cli.js
CHANGED
|
@@ -8,7 +8,7 @@ const program = new Command()
|
|
|
8
8
|
.name('search-indexer')
|
|
9
9
|
.description('Run the object-grain search indexer: select datasets, extract and project each root type per the mounted schema module, and rebuild the Typesense collections. Configuration comes from environment variables; see the README.')
|
|
10
10
|
.version(version)
|
|
11
|
-
.argument('[datasets...]', 'dataset IRIs to index (overrides the DATASETS environment variable)')
|
|
11
|
+
.argument('[datasets...]', 'dataset IRIs and file: URLs to index (overrides the DATASETS environment variable)')
|
|
12
12
|
.option('--check', 'validate the configuration and schema module, then exit without indexing');
|
|
13
13
|
program.parse();
|
|
14
14
|
try {
|
package/dist/composition.d.ts
CHANGED
|
@@ -3,7 +3,10 @@ import type { RootType, SearchDocument, SearchSchema } from '@lde/search';
|
|
|
3
3
|
import { type QleverOptions } from '@lde/sparql-qlever';
|
|
4
4
|
import type { Client } from 'typesense';
|
|
5
5
|
import type { IndexerConfig } from './config.js';
|
|
6
|
-
/**
|
|
6
|
+
/**
|
|
7
|
+
* The dataset selection the configuration describes: what the registry selects,
|
|
8
|
+
* followed by the local files and directories it names.
|
|
9
|
+
*/
|
|
7
10
|
export declare function datasetSelectorFrom(config: IndexerConfig): DatasetSelector;
|
|
8
11
|
/**
|
|
9
12
|
* The engine-writer factory the configuration describes: an
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"composition.d.ts","sourceRoot":"","sources":["../src/composition.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"composition.d.ts","sourceRoot":"","sources":["../src/composition.ts"],"names":[],"mappings":"AAKA,OAAO,EAOL,KAAK,eAAe,EACpB,KAAK,oBAAoB,EACzB,KAAK,MAAM,EACZ,MAAM,eAAe,CAAC;AACvB,OAAO,KAAK,EACV,QAAQ,EACR,cAAc,EACd,YAAY,EAEb,MAAM,aAAa,CAAC;AAMrB,OAAO,EAAgB,KAAK,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACtE,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AACxC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAEjD;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,aAAa,GAAG,eAAe,CAiB1E;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,aAAa,GACpB,CAAC,UAAU,EAAE,QAAQ,EAAE,MAAM,EAAE,YAAY,KAAK,MAAM,CAAC,cAAc,CAAC,CAmBxE;AAED;;;;;;;;;GASG;AACH,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,aAAa,EACrB,oBAAoB,CAAC,EAAE,aAAa,CAAC,sBAAsB,CAAC,GAC3D,oBAAoB,GAAG,SAAS,CAmClC"}
|
package/dist/composition.js
CHANGED
|
@@ -1,16 +1,28 @@
|
|
|
1
1
|
// The composition pieces of createSearchIndexer, each usable on its own so a
|
|
2
2
|
// deployment that needs one bespoke stage keeps the rest of the wiring
|
|
3
3
|
// (Pipeline keeps its own wiring private).
|
|
4
|
+
import { Dataset } from '@lde/dataset';
|
|
4
5
|
import { Client as RegistryClient } from '@lde/dataset-registry-client';
|
|
5
|
-
import { ImportResolver, RegistrySelector, SparqlDistributionResolver, } from '@lde/pipeline';
|
|
6
|
+
import { CombinedSelector, DirectorySelector, ImportResolver, ManualDatasetSelection, RegistrySelector, SparqlDistributionResolver, } from '@lde/pipeline';
|
|
6
7
|
import { BlueGreenRebuild, deriveCollectionName, InPlaceRebuild, } from '@lde/search-typesense';
|
|
7
8
|
import { createQlever } from '@lde/sparql-qlever';
|
|
8
|
-
/**
|
|
9
|
+
/**
|
|
10
|
+
* The dataset selection the configuration describes: what the registry selects,
|
|
11
|
+
* followed by the local files and directories it names.
|
|
12
|
+
*/
|
|
9
13
|
export function datasetSelectorFrom(config) {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
+
const selectors = config.localSources.map((path) => path.endsWith('/')
|
|
15
|
+
? new DirectorySelector(path)
|
|
16
|
+
: new ManualDatasetSelection([Dataset.localFile(path)]));
|
|
17
|
+
if (config.registryEndpoint) {
|
|
18
|
+
selectors.unshift(new RegistrySelector({
|
|
19
|
+
registry: new RegistryClient(config.registryEndpoint),
|
|
20
|
+
criteria: config.datasetCriteria,
|
|
21
|
+
}));
|
|
22
|
+
}
|
|
23
|
+
return selectors.length === 1
|
|
24
|
+
? selectors[0]
|
|
25
|
+
: new CombinedSelector(selectors);
|
|
14
26
|
}
|
|
15
27
|
/**
|
|
16
28
|
* The engine-writer factory the configuration describes: an
|
package/dist/config.d.ts
CHANGED
|
@@ -9,8 +9,9 @@ export interface IndexerConfig {
|
|
|
9
9
|
/** Path of the mounted schema-declaration module (`SCHEMA_MODULE`). */
|
|
10
10
|
readonly schemaModulePath: string;
|
|
11
11
|
/** SPARQL endpoint of the DCAT dataset registry the selection queries
|
|
12
|
-
* (`REGISTRY_ENDPOINT`).
|
|
13
|
-
|
|
12
|
+
* (`REGISTRY_ENDPOINT`). Undefined when `DATASETS` names only local data, so
|
|
13
|
+
* the run has no registry to read. */
|
|
14
|
+
readonly registryEndpoint?: URL;
|
|
14
15
|
/**
|
|
15
16
|
* Root types extracted from {@link registryEndpoint} instead of from each
|
|
16
17
|
* dataset’s own distribution (`REGISTRY_ROOT_TYPES`, whitespace- or
|
|
@@ -24,6 +25,10 @@ export interface IndexerConfig {
|
|
|
24
25
|
* search criteria (`DATASET_CRITERIA`), or every dataset when neither is
|
|
25
26
|
* set. */
|
|
26
27
|
readonly datasetCriteria: SearchCriteria;
|
|
28
|
+
/** Local data to index besides what the registry selects, from the `file:`
|
|
29
|
+
* URLs in `DATASETS`: paths of files, each one dataset, and of directories,
|
|
30
|
+
* which end in `/`, with one dataset per RDF file. */
|
|
31
|
+
readonly localSources: readonly string[];
|
|
27
32
|
/** Where the Typesense engine writes (`TYPESENSE_*`). */
|
|
28
33
|
readonly typesense: TypesenseConnection;
|
|
29
34
|
/** How each type’s collection is rebuilt (`REBUILD_MODE`): update the live
|
package/dist/config.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAInE;;;;;GAKG;AACH,MAAM,WAAW,aAAa;IAC5B,uEAAuE;IACvE,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAClC;;2CAEuC;IACvC,QAAQ,CAAC,gBAAgB,CAAC,EAAE,GAAG,CAAC;IAChC;;;;;;;OAOG;IACH,QAAQ,CAAC,iBAAiB,EAAE,SAAS,MAAM,EAAE,CAAC;IAC9C;;eAEW;IACX,QAAQ,CAAC,eAAe,EAAE,cAAc,CAAC;IACzC;;2DAEuD;IACvD,QAAQ,CAAC,YAAY,EAAE,SAAS,MAAM,EAAE,CAAC;IACzC,yDAAyD;IACzD,QAAQ,CAAC,SAAS,EAAE,mBAAmB,CAAC;IACxC;wEACoE;IACpE,QAAQ,CAAC,WAAW,EAAE,UAAU,GAAG,YAAY,CAAC;IAChD;wEACoE;IACpE,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IACnC;;;;;OAKG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC;oDACgD;IAChD,QAAQ,CAAC,UAAU,CAAC,EAAE,gBAAgB,CAAC;IACvC;;;iBAGa;IACb,QAAQ,CAAC,MAAM,CAAC,EAAE,YAAY,CAAC;CAChC;AAED,sEAAsE;AACtE,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC;IACpC,uEAAuE;IACvE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED;sEACsE;AACtE,MAAM,WAAW,gBAAgB;IAC/B,sEAAsE;IACtE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,yEAAyE;IACzE,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;CAClC;AAED;;;GAGG;AACH,MAAM,MAAM,YAAY,GAAG;IACzB,sEAAsE;IACtE,QAAQ,CAAC,QAAQ,EAAE,QAAQ,GAAG,0BAA0B,GAAG,QAAQ,CAAC;IACpE,8DAA8D;IAC9D,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B,GAAG,CACA;IACE,qEAAqE;IACrE,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IACxB,mEAAmE;IACnE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB;;;iDAG6C;IAC7C,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;CAC3B,GACD;IACE;8DAC0D;IAC1D,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;CACzB,CACJ,CAAC;AAUF;;;;GAIG;AACH,wBAAgB,qBAAqB,CACnC,WAAW,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC,GACxD,aAAa,CAkGf"}
|
package/dist/config.js
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { Dataset } from '@lde/dataset';
|
|
2
|
+
import { dirname } from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
1
4
|
const QLEVER_MODES = ['docker', 'remote'];
|
|
2
5
|
const IMPORT_STRATEGIES = [
|
|
3
6
|
'sparql',
|
|
@@ -43,7 +46,13 @@ export function configFromEnvironment(environment) {
|
|
|
43
46
|
return new URL('http://invalid.invalid');
|
|
44
47
|
}
|
|
45
48
|
};
|
|
46
|
-
const
|
|
49
|
+
const datasets = names(environment['DATASETS']);
|
|
50
|
+
const registryEndpoint = datasets.length > 0 && datasets.every(isLocal)
|
|
51
|
+
? undefined
|
|
52
|
+
: url('REGISTRY_ENDPOINT', required('REGISTRY_ENDPOINT'));
|
|
53
|
+
if (registryEndpoint?.protocol === 'file:') {
|
|
54
|
+
problems.push('REGISTRY_ENDPOINT must be a SPARQL endpoint: name local files and directories in DATASETS');
|
|
55
|
+
}
|
|
47
56
|
const protocol = environment['TYPESENSE_PROTOCOL'] ?? 'http';
|
|
48
57
|
if (protocol !== 'http' && protocol !== 'https') {
|
|
49
58
|
problems.push(`TYPESENSE_PROTOCOL must be “http” or “https”, got “${protocol}”`);
|
|
@@ -52,14 +61,16 @@ export function configFromEnvironment(environment) {
|
|
|
52
61
|
if (rebuildMode !== 'in-place' && rebuildMode !== 'blue-green') {
|
|
53
62
|
problems.push(`REBUILD_MODE must be “in-place” or “blue-green”, got “${rebuildMode}”`);
|
|
54
63
|
}
|
|
55
|
-
const datasetCriteria = criteriaFromEnvironment(environment, problems);
|
|
64
|
+
const datasetCriteria = criteriaFromEnvironment(environment, datasets, problems);
|
|
56
65
|
const provenance = provenanceFromEnvironment(environment, rebuildMode, problems);
|
|
57
66
|
const qlever = qleverFromEnvironment(environment, problems);
|
|
67
|
+
const localSources = localSourcesFrom(datasets, qlever, problems);
|
|
58
68
|
const config = {
|
|
59
69
|
schemaModulePath: environment['SCHEMA_MODULE'] ?? '/config/search-schema.mjs',
|
|
60
70
|
registryEndpoint,
|
|
61
71
|
registryRootTypes: names(environment['REGISTRY_ROOT_TYPES']),
|
|
62
72
|
datasetCriteria,
|
|
73
|
+
localSources,
|
|
63
74
|
typesense: {
|
|
64
75
|
host: required('TYPESENSE_HOST'),
|
|
65
76
|
port: integer('TYPESENSE_PORT', 8108),
|
|
@@ -87,17 +98,53 @@ function names(value) {
|
|
|
87
98
|
? []
|
|
88
99
|
: value.split(/[\s,]+/).filter((name) => name.length > 0);
|
|
89
100
|
}
|
|
90
|
-
/** `DATASETS`
|
|
91
|
-
*
|
|
92
|
-
function
|
|
93
|
-
|
|
101
|
+
/** Whether a `DATASETS` entry names local data rather than a registered dataset.
|
|
102
|
+
* Parsed, because a URL scheme is case-insensitive. */
|
|
103
|
+
function isLocal(entry) {
|
|
104
|
+
return URL.parse(entry)?.protocol === 'file:';
|
|
105
|
+
}
|
|
106
|
+
/** The paths of the `file:` URLs in `DATASETS`. Only QLever can read a file, so
|
|
107
|
+
* they require the import path. */
|
|
108
|
+
function localSourcesFrom(datasets, qlever, problems) {
|
|
109
|
+
const entries = datasets.filter(isLocal);
|
|
110
|
+
if (entries.length > 0 && !qlever) {
|
|
111
|
+
problems.push('A file: URL in DATASETS requires QLEVER_IMAGE or QLEVER_MODE=remote: a file must be imported before it can be queried');
|
|
112
|
+
}
|
|
113
|
+
const paths = entries.flatMap((entry) => {
|
|
114
|
+
try {
|
|
115
|
+
const path = fileURLToPath(entry);
|
|
116
|
+
if (!path.endsWith('/')) {
|
|
117
|
+
Dataset.localFile(path);
|
|
118
|
+
}
|
|
119
|
+
return [path];
|
|
120
|
+
}
|
|
121
|
+
catch (error) {
|
|
122
|
+
problems.push(`DATASETS contains “${entry}”, which is not a local RDF file or a directory ending in /: ${error.message}`);
|
|
123
|
+
return [];
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
const directories = new Set(paths.filter((path) => path.endsWith('/')));
|
|
127
|
+
paths.forEach((path, index) => {
|
|
128
|
+
if (paths.indexOf(path) !== index) {
|
|
129
|
+
problems.push(`DATASETS lists “${path}” twice`);
|
|
130
|
+
}
|
|
131
|
+
else if (!path.endsWith('/') && directories.has(`${dirname(path)}/`)) {
|
|
132
|
+
problems.push(`DATASETS lists “${path}” and its directory, which already selects it`);
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
return paths;
|
|
136
|
+
}
|
|
137
|
+
/** `DATASETS` (whitespace-separated IRIs, of which `file:` URLs are local data
|
|
138
|
+
* rather than registered datasets) or `DATASET_CRITERIA` (a JSON object) –
|
|
139
|
+
* never both; neither selects the whole registry. */
|
|
140
|
+
function criteriaFromEnvironment(environment, datasets, problems) {
|
|
94
141
|
const criteriaJson = environment['DATASET_CRITERIA'];
|
|
95
|
-
if (
|
|
142
|
+
if (environment['DATASETS'] && criteriaJson) {
|
|
96
143
|
problems.push('DATASETS and DATASET_CRITERIA are mutually exclusive; set at most one');
|
|
97
144
|
return {};
|
|
98
145
|
}
|
|
99
|
-
if (
|
|
100
|
-
const iris = datasets.
|
|
146
|
+
if (environment['DATASETS']) {
|
|
147
|
+
const iris = datasets.filter((iri) => !isLocal(iri));
|
|
101
148
|
for (const iri of iris) {
|
|
102
149
|
try {
|
|
103
150
|
new URL(iri);
|
package/dist/indexer.js
CHANGED
|
@@ -34,7 +34,7 @@ export async function createSearchIndexer(config, extensions = {}) {
|
|
|
34
34
|
datasets: datasetSelectorFrom(config),
|
|
35
35
|
distributionResolver: distributionResolverFrom(config, extensions.replaceJsonLdContext),
|
|
36
36
|
writerFor: writerFactoryFrom(client, config),
|
|
37
|
-
registryTypes: config.registryRootTypes.length > 0
|
|
37
|
+
registryTypes: config.registryEndpoint && config.registryRootTypes.length > 0
|
|
38
38
|
? {
|
|
39
39
|
endpoint: config.registryEndpoint,
|
|
40
40
|
names: config.registryRootTypes,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lde/search-indexer",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.28.0",
|
|
4
4
|
"description": "The @lde/search-pipeline indexer as a bootable process and prebuilt Docker image: loads a mounted schema-declaration module, dataset selection, Typesense connection and rebuild mode from the environment, then runs the object-grain searchIndexerPipeline once to completion. The composition layer that binds the engine-agnostic @lde/search-pipeline stages to @lde/search-typesense – the write-path counterpart of @lde/search-api-server.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"url": "git+https://github.com/ldelements/lde.git",
|
|
@@ -28,13 +28,14 @@
|
|
|
28
28
|
"!**/*.tsbuildinfo"
|
|
29
29
|
],
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"@lde/dataset
|
|
32
|
-
"@lde/
|
|
33
|
-
"@lde/pipeline
|
|
31
|
+
"@lde/dataset": "^0.10.0",
|
|
32
|
+
"@lde/dataset-registry-client": "^0.12.0",
|
|
33
|
+
"@lde/pipeline": "^0.41.0",
|
|
34
|
+
"@lde/pipeline-console-reporter": "^0.32.0",
|
|
34
35
|
"@lde/search": "^0.34.0",
|
|
35
|
-
"@lde/search-pipeline": "^0.
|
|
36
|
-
"@lde/search-typesense": "^0.
|
|
37
|
-
"@lde/sparql-qlever": "^0.
|
|
36
|
+
"@lde/search-pipeline": "^0.38.0",
|
|
37
|
+
"@lde/search-typesense": "^0.41.0",
|
|
38
|
+
"@lde/sparql-qlever": "^0.20.0",
|
|
38
39
|
"commander": "^15.0.0",
|
|
39
40
|
"tslib": "^2.3.0",
|
|
40
41
|
"typesense": "^3.1.1"
|