@lde/sparql-qlever 0.15.3 → 0.15.5
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 +6 -46
- package/dist/preprocess.d.ts +4 -4
- package/dist/preprocess.d.ts.map +1 -1
- package/dist/preprocess.js +29 -13
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,45 +1,13 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @lde/sparql-qlever
|
|
2
2
|
|
|
3
3
|
An adapter for the [QLever](https://github.com/ad-freiburg/qlever) SPARQL server.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Installation
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
Only **one** index is cached at a time. In a multi-dataset pipeline, each dataset overwrites the previous index. On re-run, the last-indexed dataset gets a cache hit while the others rebuild.
|
|
10
|
-
|
|
11
|
-
Caching is enabled by default. Disable it by passing `cacheIndex: false` to `createQlever()` or the `Importer` constructor (e.g. driven by a `QLEVER_CACHE_INDEX=false` environment variable).
|
|
12
|
-
|
|
13
|
-
## Networking (Docker mode)
|
|
14
|
-
|
|
15
|
-
By default `createQlever({ mode: 'docker' })` publishes QLever’s port on the host and addresses the query endpoint as `localhost` – correct when the consumer runs on the host, or shares the QLever container’s network namespace. A consumer that itself runs in a container on a bridge network cannot reach a host-published port; pass `network` (a Docker network the consumer is attached to) together with `containerName`, and QLever joins that network with the endpoint addressed by container name instead:
|
|
16
|
-
|
|
17
|
-
```ts
|
|
18
|
-
const { importer, server } = createQlever({
|
|
19
|
-
mode: 'docker',
|
|
20
|
-
image: 'adfreiburg/qlever:latest',
|
|
21
|
-
network: 'app_default',
|
|
22
|
-
containerName: 'qlever',
|
|
23
|
-
});
|
|
24
|
-
server.queryEndpoint.toString(); // 'http://qlever:7001/sparql'
|
|
7
|
+
```sh
|
|
8
|
+
npm install @lde/sparql-qlever
|
|
25
9
|
```
|
|
26
10
|
|
|
27
|
-
## Configuration
|
|
28
|
-
|
|
29
|
-
`createQlever()` accepts `indexOptions` and `serverOptions` to tune QLever's index builder and server respectively.
|
|
30
|
-
|
|
31
|
-
### Server options (`serverOptions`)
|
|
32
|
-
|
|
33
|
-
Passed to `qlever-server` at startup.
|
|
34
|
-
|
|
35
|
-
| Option | Description | Default |
|
|
36
|
-
| ----------------------- | ------------------------------------------------ | ------- |
|
|
37
|
-
| `memory-max-size` | Maximum memory for query processing and caching. | `'4G'` |
|
|
38
|
-
| `default-query-timeout` | Default query timeout. | `'30s'` |
|
|
39
|
-
| `cache-max-size` | Maximum cache size for query results. | – |
|
|
40
|
-
|
|
41
|
-
Example:
|
|
42
|
-
|
|
43
11
|
```ts
|
|
44
12
|
const { importer, server } = createQlever({
|
|
45
13
|
mode: 'docker',
|
|
@@ -51,14 +19,6 @@ const { importer, server } = createQlever({
|
|
|
51
19
|
});
|
|
52
20
|
```
|
|
53
21
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
Passed to `qlever-index` during import.
|
|
22
|
+
## Documentation
|
|
57
23
|
|
|
58
|
-
|
|
59
|
-
| ------------------------------- | --------------------------------------------------------------------------------------------- | ----------- |
|
|
60
|
-
| `ascii-prefixes-only` | Enable faster parsing for well-behaved TTL files. | `true` |
|
|
61
|
-
| `num-triples-per-batch` | Triples per batch; lower values reduce memory usage. | `3_000_000` |
|
|
62
|
-
| `stxxl-memory` | Memory budget for sorting during the index build. | `'10G'` |
|
|
63
|
-
| `parallel-parsing` | Parse input in parallel. | `true` |
|
|
64
|
-
| `only-pso-and-pos-permutations` | Build only PSO and POS permutations. Faster, but queries with predicate variables won't work. | `false` |
|
|
24
|
+
See the [full documentation](https://ldelements.org/reference/sparql-qlever).
|
package/dist/preprocess.d.ts
CHANGED
|
@@ -12,8 +12,8 @@ export interface PreprocessResult {
|
|
|
12
12
|
* JSON-LD, RDF/XML and TriG distributions return `true`: `qlever-index` cannot
|
|
13
13
|
* parse any of them, so we stream them through `rdf-parse` into N-Quads first.
|
|
14
14
|
*
|
|
15
|
-
* Native RDF formats (`nt`, `nq`, `ttl`)
|
|
16
|
-
* `application/gzip` or `application/zip`
|
|
15
|
+
* Native RDF formats (`nt`, `nq`, `ttl`) – including when wrapped in
|
|
16
|
+
* `application/gzip` or `application/zip` – go straight through the shell
|
|
17
17
|
* pipeline in `index()`, which uses `gunzip -c` or `unzip -p` as appropriate.
|
|
18
18
|
* Standalone `mediaType=application/zip` is rejected upstream: the inner
|
|
19
19
|
* format must be declared.
|
|
@@ -25,8 +25,8 @@ export declare function needsPreprocessing(distribution: Distribution): boolean;
|
|
|
25
25
|
*
|
|
26
26
|
* Streams the source through `rdf-parse` → `rdf-serialize` so memory use
|
|
27
27
|
* stays bounded regardless of input size. Handles gzip transparently
|
|
28
|
-
* (
|
|
29
|
-
* every parseable entry into the output stream in order).
|
|
28
|
+
* (detected from the file’s magic bytes, not from declared metadata) and zip
|
|
29
|
+
* containers (folds every parseable entry into the output stream in order).
|
|
30
30
|
*
|
|
31
31
|
* Cached: if the output is newer than the input, it is reused as-is.
|
|
32
32
|
*/
|
package/dist/preprocess.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"preprocess.d.ts","sourceRoot":"","sources":["../src/preprocess.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"preprocess.d.ts","sourceRoot":"","sources":["../src/preprocess.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AA2B5C,MAAM,WAAW,gBAAgB;IAC/B,iEAAiE;IACjE,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,IAAI,CAAC;IACb,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,kBAAkB,CAAC,YAAY,EAAE,YAAY,GAAG,OAAO,CAKtE;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,UAAU,CAC9B,SAAS,EAAE,MAAM,EACjB,YAAY,EAAE,YAAY,GACzB,OAAO,CAAC,gBAAgB,CAAC,CAyB3B"}
|
package/dist/preprocess.js
CHANGED
|
@@ -2,7 +2,7 @@ import { rdfParser } from 'rdf-parse';
|
|
|
2
2
|
import { rdfSerializer } from 'rdf-serialize';
|
|
3
3
|
import { createGunzip } from 'node:zlib';
|
|
4
4
|
import { createReadStream, createWriteStream } from 'node:fs';
|
|
5
|
-
import { rm, stat } from 'node:fs/promises';
|
|
5
|
+
import { open, rm, stat } from 'node:fs/promises';
|
|
6
6
|
import { finished } from 'node:stream/promises';
|
|
7
7
|
import { promisify } from 'node:util';
|
|
8
8
|
import yauzl from 'yauzl';
|
|
@@ -10,8 +10,6 @@ const JSONLD_MIME = 'application/ld+json';
|
|
|
10
10
|
const RDFXML_MIME = 'application/rdf+xml';
|
|
11
11
|
const TRIG_MIME = 'application/trig';
|
|
12
12
|
const ZIP_MIME = 'application/zip';
|
|
13
|
-
const GZIP_MIME = 'application/gzip';
|
|
14
|
-
const GZIP_MIME_LEGACY = 'application/x-gzip';
|
|
15
13
|
/**
|
|
16
14
|
* RDF media types `qlever-index` cannot read natively, keyed by `rdf-parse`
|
|
17
15
|
* `contentType` with a label for warnings. TriG belongs here too: its
|
|
@@ -29,8 +27,8 @@ const preprocessFormats = new Map([
|
|
|
29
27
|
* JSON-LD, RDF/XML and TriG distributions return `true`: `qlever-index` cannot
|
|
30
28
|
* parse any of them, so we stream them through `rdf-parse` into N-Quads first.
|
|
31
29
|
*
|
|
32
|
-
* Native RDF formats (`nt`, `nq`, `ttl`)
|
|
33
|
-
* `application/gzip` or `application/zip`
|
|
30
|
+
* Native RDF formats (`nt`, `nq`, `ttl`) – including when wrapped in
|
|
31
|
+
* `application/gzip` or `application/zip` – go straight through the shell
|
|
34
32
|
* pipeline in `index()`, which uses `gunzip -c` or `unzip -p` as appropriate.
|
|
35
33
|
* Standalone `mediaType=application/zip` is rejected upstream: the inner
|
|
36
34
|
* format must be declared.
|
|
@@ -45,8 +43,8 @@ export function needsPreprocessing(distribution) {
|
|
|
45
43
|
*
|
|
46
44
|
* Streams the source through `rdf-parse` → `rdf-serialize` so memory use
|
|
47
45
|
* stays bounded regardless of input size. Handles gzip transparently
|
|
48
|
-
* (
|
|
49
|
-
* every parseable entry into the output stream in order).
|
|
46
|
+
* (detected from the file’s magic bytes, not from declared metadata) and zip
|
|
47
|
+
* containers (folds every parseable entry into the output stream in order).
|
|
50
48
|
*
|
|
51
49
|
* Cached: if the output is newer than the input, it is reused as-is.
|
|
52
50
|
*/
|
|
@@ -66,7 +64,7 @@ export async function preprocess(localFile, distribution) {
|
|
|
66
64
|
await streamRdfZip(localFile, outputFile, contentType, label, warnings);
|
|
67
65
|
}
|
|
68
66
|
else {
|
|
69
|
-
await streamRdfFile(localFile, outputFile, contentType
|
|
67
|
+
await streamRdfFile(localFile, outputFile, contentType);
|
|
70
68
|
}
|
|
71
69
|
return { path: outputFile, format: 'nq', warnings };
|
|
72
70
|
}
|
|
@@ -102,12 +100,11 @@ async function closeWritable(output) {
|
|
|
102
100
|
output.end();
|
|
103
101
|
});
|
|
104
102
|
}
|
|
105
|
-
async function streamRdfFile(localFile, outputFile, contentType
|
|
106
|
-
const isGzipped = distribution.compressMimeType === GZIP_MIME ||
|
|
107
|
-
distribution.compressMimeType === GZIP_MIME_LEGACY ||
|
|
108
|
-
localFile.toLowerCase().endsWith('.gz');
|
|
103
|
+
async function streamRdfFile(localFile, outputFile, contentType) {
|
|
109
104
|
const source = createReadStream(localFile);
|
|
110
|
-
const input =
|
|
105
|
+
const input = (await isGzipFile(localFile))
|
|
106
|
+
? source.pipe(createGunzip())
|
|
107
|
+
: source;
|
|
111
108
|
const output = createWriteStream(outputFile);
|
|
112
109
|
try {
|
|
113
110
|
await pipeRdfToWritable(input, output, contentType);
|
|
@@ -116,6 +113,25 @@ async function streamRdfFile(localFile, outputFile, contentType, distribution) {
|
|
|
116
113
|
await closeWritable(output);
|
|
117
114
|
}
|
|
118
115
|
}
|
|
116
|
+
/**
|
|
117
|
+
* Whether the file begins with the gzip magic number (RFC 1952 §2.3.1).
|
|
118
|
+
*
|
|
119
|
+
* Sniffing the bytes is the only reliable signal for a downloaded dump.
|
|
120
|
+
* `dcat:compressFormat` is frequently absent, and the local filename is derived
|
|
121
|
+
* from the access URL, so a dump behind a query string (`…/download.trig.gz?graph=…`)
|
|
122
|
+
* carries no `.gz` suffix. Conversely, a `.gz` name says nothing once the server
|
|
123
|
+
* declared a real `Content-Encoding: gzip` and `fetch` already inflated the body.
|
|
124
|
+
*/
|
|
125
|
+
async function isGzipFile(path) {
|
|
126
|
+
const file = await open(path);
|
|
127
|
+
try {
|
|
128
|
+
const { buffer, bytesRead } = await file.read(Buffer.alloc(2), 0, 2, 0);
|
|
129
|
+
return bytesRead === 2 && buffer[0] === 0x1f && buffer[1] === 0x8b;
|
|
130
|
+
}
|
|
131
|
+
finally {
|
|
132
|
+
await file.close();
|
|
133
|
+
}
|
|
134
|
+
}
|
|
119
135
|
const openZip = promisify(yauzl.open);
|
|
120
136
|
/**
|
|
121
137
|
* Fold every parseable entry of a zip into the N-Quads output, in order. The
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lde/sparql-qlever",
|
|
3
|
-
"version": "0.15.
|
|
3
|
+
"version": "0.15.5",
|
|
4
4
|
"repository": {
|
|
5
5
|
"url": "git+https://github.com/ldelements/lde.git",
|
|
6
6
|
"directory": "packages/sparql-qlever"
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"@lde/task-runner": "^0.2.13",
|
|
32
32
|
"@lde/task-runner-docker": "^0.2.16",
|
|
33
33
|
"@lde/task-runner-native": "^0.2.16",
|
|
34
|
-
"@lde/wait-for-sparql": "^0.2.
|
|
34
|
+
"@lde/wait-for-sparql": "^0.2.17",
|
|
35
35
|
"rdf-parse": "^5.0.0",
|
|
36
36
|
"rdf-serialize": "^5.1.0",
|
|
37
37
|
"tslib": "^2.3.0",
|