@lde/search-indexer 0.0.2 → 0.0.4

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 CHANGED
@@ -7,18 +7,11 @@ projects each root type per the schema, and rebuilds the engine collections –
7
7
  then exits. Run it as a cron job; the rebuild writers’ cross-pod lock makes an
8
8
  overlapping run fail fast instead of corrupting a collection.
9
9
 
10
- This is the write-path counterpart of
11
- [`@lde/search-api-server`](../search-api-server)
12
- ([#652](https://github.com/ldelements/lde/issues/652)): the composition layer
13
- that binds the engine-agnostic `searchIndexerPipeline` to the
14
- [`@lde/search-typesense`](../search-typesense) rebuild writers. Mount the
15
- **same schema module** into both images and point both at the same
16
- `TYPESENSE_*` coordinates, and the write and the read side cannot disagree
17
- about the schema. A deployment that needs more – a bespoke root selector,
18
- per-stage tuning, another engine – composes `searchStages`,
19
- `searchIndexWriter` and `Pipeline` directly instead.
10
+ ## Installation
20
11
 
21
- ## Run
12
+ ```sh
13
+ npm install @lde/search-indexer
14
+ ```
22
15
 
23
16
  ```sh
24
17
  docker run --rm \
@@ -29,109 +22,6 @@ docker run --rm \
29
22
  ghcr.io/ldelements/search-indexer
30
23
  ```
31
24
 
32
- Or without Docker (the same environment variables apply):
33
-
34
- ```sh
35
- npx @lde/search-indexer
36
- ```
37
-
38
- Dataset IRIs can also be passed as arguments, which override `DATASETS`:
39
-
40
- ```sh
41
- npx @lde/search-indexer https://example.org/id/dataset/1
42
- ```
43
-
44
- `--check` validates the configuration and schema module, then exits without
45
- indexing – for CI and init containers.
46
-
47
- ## The schema module
48
-
49
- The mounted module default-exports the deployment’s search type declarations
50
- as **plain data** – see [the API server’s
51
- README](../search-api-server/README.md#the-schema-module) for the format and
52
- authoring guidance; both images load the module with the same
53
- `@lde/search/module` loader. The indexer reads only the default export: the
54
- declarations drive one pipeline stage and one engine collection per root type,
55
- and each field’s `path` drives the extraction CONSTRUCT. The read-side extras
56
- (`schemaOptions`, `engineOptions`) are ignored here, so one file serves both
57
- images.
58
-
59
- ## Configuration
60
-
61
- | Variable | Default | Meaning |
62
- | -------------------- | --------------------------- | ---------------------------------------------------------------------------- |
63
- | `SCHEMA_MODULE` | `/config/search-schema.mjs` | Path of the mounted schema-declaration module |
64
- | `REGISTRY_ENDPOINT` | **required** | SPARQL endpoint of the DCAT dataset registry |
65
- | `DATASETS` | all registry datasets | Dataset IRIs to index (whitespace- or comma-separated) |
66
- | `DATASET_CRITERIA` | all registry datasets | Registry search criteria as a JSON object (mutually exclusive w/ `DATASETS`) |
67
- | `TYPESENSE_HOST` | **required** | Typesense host |
68
- | `TYPESENSE_PORT` | `8108` | Typesense port |
69
- | `TYPESENSE_PROTOCOL` | `http` | `http` or `https` |
70
- | `TYPESENSE_API_KEY` | **required** | An admin key: the indexer creates, writes and swaps collections |
71
- | `REBUILD_MODE` | `in-place` | `in-place` (update the live collection) or `blue-green` (swap on commit) |
72
- | `COLLECTION_PREFIX` | none | Prefix for every derived collection name (configure the read side to match) |
73
- | `PROVENANCE_FILE` | none | JSON file remembering per-dataset processing, to skip unchanged datasets |
74
- | `PIPELINE_VERSION` | none | Version keying the skip decisions; required with `PROVENANCE_FILE` |
75
- | `QLEVER_IMAGE` | none | Enables the QLever import path (see below), e.g. `adfreiburg/qlever:latest` |
76
- | `IMPORT_STRATEGY` | `sparql` | `sparql`, `sparqlWithImportFallback` or `import`; requires `QLEVER_IMAGE` |
77
- | `DATA_DIR` | `/data` | Directory for downloaded dumps and QLever index caches |
78
-
79
- A misconfigured boot reports **all** problems in one error, not one per crash
80
- loop. `PROVENANCE_FILE` must sit on a durable volume, and cannot be combined
81
- with `REBUILD_MODE=blue-green`: a skipped dataset would be missing from the
82
- fresh collection the swap makes live. Its directory must be writable by the
83
- image’s runtime user (uid 1000) – the image pre-creates `/provenance` with
84
- that ownership, so a named volume mounted there just works, while a bind
85
- mount must be `chown`ed on the host. A run fails at start when the file is
86
- not writable, instead of silently never skipping.
25
+ ## Documentation
87
26
 
88
- ## The QLever import path
89
-
90
- By default the indexer serves only datasets that publish a live SPARQL
91
- endpoint (the pipeline’s endpoint-only default). Setting `QLEVER_IMAGE`
92
- enables the import path: data dumps are downloaded to `DATA_DIR` and imported
93
- into a QLever instance the **pipeline itself creates and controls** – one
94
- sibling container per dataset, spawned over the Docker socket
95
- ([ADR 16](../../docs/decisions/0016-ship-the-search-indexer-as-a-config-driven-image.md)
96
- explains why a statically-declared QLever service cannot work). That requires:
97
-
98
- - the Docker socket mounted into the indexer container
99
- (`--volume /var/run/docker.sock:/var/run/docker.sock`), plus a group grant
100
- to reach it as the image’s non-root user
101
- (`--group-add $(stat -c %g /var/run/docker.sock)`);
102
- - `DATA_DIR` on a volume whose **host path is identical** for the indexer and
103
- the spawned QLever containers – the pipeline passes `DATA_DIR` as a bind
104
- mount to a sibling container, where it resolves against the host.
105
-
106
- This mode does not work on container runtimes without a Docker socket
107
- (containerd-based Kubernetes); there, run endpoint-only for now.
108
-
109
- ## Building the image
110
-
111
- The image is built from the workspace’s own outputs – the compiled package,
112
- the same-commit builds of its `@lde/*` dependencies and a pruned lockfile –
113
- never from npm, so it exists for any commit
114
- ([ADR 15](../../docs/decisions/0015-ship-the-served-search-api-as-a-docker-image-built-from-the-workspace.md)):
115
-
116
- ```sh
117
- npx nx run @lde/search-indexer:docker:build # → packages-search-indexer
118
- npx nx run @lde/search-indexer:docker:smoke # boots it with --check
119
- ```
120
-
121
- CI runs `docker:smoke` for affected PRs; each release rebuilds and pushes
122
- `ghcr.io/ldelements/search-indexer:<version>` (`.github/workflows/docker.yml`).
123
-
124
- ## Programmatic use
125
-
126
- The bin is a thin wrapper over the exported API, usable in tests or a custom
127
- boot:
128
-
129
- ```ts
130
- import {
131
- configFromEnvironment,
132
- createSearchIndexer,
133
- } from '@lde/search-indexer';
134
-
135
- const pipeline = await createSearchIndexer(configFromEnvironment(process.env));
136
- await pipeline.run();
137
- ```
27
+ See the [full documentation](https://ldelements.org/reference/search-indexer).
@@ -1 +1 @@
1
- {"version":3,"file":"composition.d.ts","sourceRoot":"","sources":["../src/composition.ts"],"names":[],"mappings":"AAGA,OAAO,EAIL,KAAK,eAAe,EACpB,KAAK,oBAAoB,EACzB,KAAK,MAAM,EACZ,MAAM,eAAe,CAAC;AACvB,OAAO,KAAK,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAO5D,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AACxC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAEjD,yEAAyE;AACzE,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,aAAa,GAAG,eAAe,CAK1E;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,aAAa,GACpB,CAAC,UAAU,EAAE,QAAQ,KAAK,MAAM,CAAC,cAAc,CAAC,CAWlD;AAED;;;;;;GAMG;AACH,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,aAAa,GACpB,oBAAoB,GAAG,SAAS,CAclC"}
1
+ {"version":3,"file":"composition.d.ts","sourceRoot":"","sources":["../src/composition.ts"],"names":[],"mappings":"AAGA,OAAO,EAIL,KAAK,eAAe,EACpB,KAAK,oBAAoB,EACzB,KAAK,MAAM,EACZ,MAAM,eAAe,CAAC;AACvB,OAAO,KAAK,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAO5D,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AACxC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAEjD,yEAAyE;AACzE,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,aAAa,GAAG,eAAe,CAK1E;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,aAAa,GACpB,CAAC,UAAU,EAAE,QAAQ,KAAK,MAAM,CAAC,cAAc,CAAC,CAWlD;AAED;;;;;;GAMG;AACH,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,aAAa,GACpB,oBAAoB,GAAG,SAAS,CA0BlC"}
@@ -40,11 +40,23 @@ export function distributionResolverFrom(config) {
40
40
  if (!config.qlever) {
41
41
  return undefined;
42
42
  }
43
- const { importer, server } = createQlever({
44
- mode: 'docker',
45
- image: config.qlever.image,
46
- dataDir: config.qlever.dataDir,
47
- });
43
+ const { importer, server } = config.qlever.network
44
+ ? createQlever({
45
+ mode: 'docker',
46
+ image: config.qlever.image,
47
+ dataDir: config.qlever.dataDir,
48
+ network: config.qlever.network,
49
+ // The network-scoped name doubles as the endpoint hostname. It keeps
50
+ // stacks on different networks from removing each other’s QLever;
51
+ // indexers sharing one network would still collide, so run at most
52
+ // one indexer per network.
53
+ containerName: `qlever-${config.qlever.network}`,
54
+ })
55
+ : createQlever({
56
+ mode: 'docker',
57
+ image: config.qlever.image,
58
+ dataDir: config.qlever.dataDir,
59
+ });
48
60
  return new ImportResolver(new SparqlDistributionResolver(), {
49
61
  importer,
50
62
  server,
package/dist/config.d.ts CHANGED
@@ -55,6 +55,11 @@ export interface QleverConfig {
55
55
  readonly strategy: 'sparql' | 'sparqlWithImportFallback' | 'import';
56
56
  /** Directory for downloaded dumps and QLever index caches. */
57
57
  readonly dataDir: string;
58
+ /** Docker network the spawned QLever joins (`QLEVER_NETWORK`), reached by
59
+ * container name. Required when the indexer itself runs containerized on
60
+ * a bridge network; without it QLever is published on the host and
61
+ * addressed as `localhost`. */
62
+ readonly network?: string;
58
63
  }
59
64
  /**
60
65
  * Read the {@link IndexerConfig} from environment variables. Reports **all**
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAEnE;;;;;GAKG;AACH,MAAM,WAAW,aAAa;IAC5B,uEAAuE;IACvE,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAClC;iCAC6B;IAC7B,QAAQ,CAAC,gBAAgB,EAAE,GAAG,CAAC;IAC/B;;eAEW;IACX,QAAQ,CAAC,eAAe,EAAE,cAAc,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;oDACgD;IAChD,QAAQ,CAAC,UAAU,CAAC,EAAE,gBAAgB,CAAC;IACvC;;uEAEmE;IACnE,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,6EAA6E;AAC7E,MAAM,WAAW,YAAY;IAC3B,mEAAmE;IACnE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,sEAAsE;IACtE,QAAQ,CAAC,QAAQ,EAAE,QAAQ,GAAG,0BAA0B,GAAG,QAAQ,CAAC;IACpE,8DAA8D;IAC9D,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAQD;;;;GAIG;AACH,wBAAgB,qBAAqB,CACnC,WAAW,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC,GACxD,aAAa,CAoFf"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAEnE;;;;;GAKG;AACH,MAAM,WAAW,aAAa;IAC5B,uEAAuE;IACvE,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAClC;iCAC6B;IAC7B,QAAQ,CAAC,gBAAgB,EAAE,GAAG,CAAC;IAC/B;;eAEW;IACX,QAAQ,CAAC,eAAe,EAAE,cAAc,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;oDACgD;IAChD,QAAQ,CAAC,UAAU,CAAC,EAAE,gBAAgB,CAAC;IACvC;;uEAEmE;IACnE,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,6EAA6E;AAC7E,MAAM,WAAW,YAAY;IAC3B,mEAAmE;IACnE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,sEAAsE;IACtE,QAAQ,CAAC,QAAQ,EAAE,QAAQ,GAAG,0BAA0B,GAAG,QAAQ,CAAC;IACpE,8DAA8D;IAC9D,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB;;;oCAGgC;IAChC,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;CAC3B;AAQD;;;;GAIG;AACH,wBAAgB,qBAAqB,CACnC,WAAW,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC,GACxD,aAAa,CAoFf"}
package/dist/config.js CHANGED
@@ -132,10 +132,14 @@ function provenanceFromEnvironment(environment, rebuildMode, problems) {
132
132
  function qleverFromEnvironment(environment, problems) {
133
133
  const image = environment['QLEVER_IMAGE'];
134
134
  const strategy = environment['IMPORT_STRATEGY'];
135
+ const network = environment['QLEVER_NETWORK'];
135
136
  if (!image) {
136
137
  if (strategy) {
137
138
  problems.push('IMPORT_STRATEGY requires QLEVER_IMAGE: without an import engine there is nothing to import into');
138
139
  }
140
+ if (network) {
141
+ problems.push('QLEVER_NETWORK requires QLEVER_IMAGE: without an import engine there is no QLever container to attach');
142
+ }
139
143
  return undefined;
140
144
  }
141
145
  if (strategy !== undefined &&
@@ -146,5 +150,6 @@ function qleverFromEnvironment(environment, problems) {
146
150
  image,
147
151
  strategy: (strategy ?? 'sparql'),
148
152
  dataDir: environment['DATA_DIR'] ?? '/data',
153
+ network,
149
154
  };
150
155
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lde/search-indexer",
3
- "version": "0.0.2",
3
+ "version": "0.0.4",
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",
@@ -31,10 +31,10 @@
31
31
  "@lde/dataset-registry-client": "^0.9.1",
32
32
  "@lde/pipeline": "^0.35.4",
33
33
  "@lde/pipeline-console-reporter": "^0.26.4",
34
- "@lde/search": "^0.12.2",
35
- "@lde/search-pipeline": "^0.12.5",
36
- "@lde/search-typesense": "^0.15.5",
37
- "@lde/sparql-qlever": "^0.15.2",
34
+ "@lde/search": "^0.12.3",
35
+ "@lde/search-pipeline": "^0.12.6",
36
+ "@lde/search-typesense": "^0.15.6",
37
+ "@lde/sparql-qlever": "^0.15.3",
38
38
  "commander": "^15.0.0",
39
39
  "tslib": "^2.3.0",
40
40
  "typesense": "^3.0.6"