@lde/sparql-qlever 0.15.4 → 0.15.6

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 +1 @@
1
- {"version":3,"file":"importer.d.ts","sourceRoot":"","sources":["../src/importer.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,QAAQ,IAAI,iBAAiB,EAC7B,eAAe,EACf,YAAY,EACZ,gBAAgB,EAChB,YAAY,EACb,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAyB,YAAY,EAAE,MAAM,cAAc,CAAC;AAMnE,MAAM,WAAW,kBAAkB;IACjC,oBAAoB;IACpB,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC,yBAAyB;IACzB,uBAAuB,CAAC,EAAE,MAAM,CAAC;IACjC,uEAAuE;IACvE,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,oBAAoB;IACpB,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,yJAAyJ;IACzJ,+BAA+B,CAAC,EAAE,OAAO,CAAC;CAC3C;AAED,MAAM,WAAW,qBAAsB,SAAQ,eAAe;IAC5D,sBAAsB;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,kBAAkB,CAAC;CACpC;AAWD,qBAAa,QAAS,YAAW,iBAAiB;IAChD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAkB;gBAE9B,OAAO,EAAE,qBAAqB;IAa7B,MAAM,CACjB,aAAa,EAAE,YAAY,EAAE,GAC5B,OAAO,CAAC,YAAY,GAAG,gBAAgB,GAAG,YAAY,CAAC;YAqC5C,QAAQ;IA6DtB,OAAO,CAAC,gBAAgB;IAQxB,OAAO,CAAC,aAAa;IAIrB;;OAEG;YACW,eAAe;IA0B7B,yDAAyD;YAC3C,eAAe;YAaf,cAAc;YAKd,KAAK;CA8CpB"}
1
+ {"version":3,"file":"importer.d.ts","sourceRoot":"","sources":["../src/importer.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,QAAQ,IAAI,iBAAiB,EAC7B,eAAe,EACf,YAAY,EACZ,gBAAgB,EAChB,YAAY,EACb,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAyB,YAAY,EAAE,MAAM,cAAc,CAAC;AAOnE,MAAM,WAAW,kBAAkB;IACjC,oBAAoB;IACpB,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC,yBAAyB;IACzB,uBAAuB,CAAC,EAAE,MAAM,CAAC;IACjC,uEAAuE;IACvE,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,oBAAoB;IACpB,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,yJAAyJ;IACzJ,+BAA+B,CAAC,EAAE,OAAO,CAAC;CAC3C;AAED,MAAM,WAAW,qBAAsB,SAAQ,eAAe;IAC5D,sBAAsB;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,kBAAkB,CAAC;CACpC;AAWD,qBAAa,QAAS,YAAW,iBAAiB;IAChD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAkB;gBAE9B,OAAO,EAAE,qBAAqB;IAa7B,MAAM,CACjB,aAAa,EAAE,YAAY,EAAE,GAC5B,OAAO,CAAC,YAAY,GAAG,gBAAgB,GAAG,YAAY,CAAC;YAqC5C,QAAQ;IA6DtB,OAAO,CAAC,gBAAgB;IAQxB,OAAO,CAAC,aAAa;IAIrB;;OAEG;YACW,eAAe;IA0B7B,yDAAyD;YAC3C,eAAe;YAaf,cAAc;YAKd,KAAK;CA8CpB"}
package/dist/importer.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { ImportFailed, ImportSuccessful, NotSupported, } from '@lde/sparql-importer';
2
2
  import { compressionMediaTypes } from '@lde/dataset';
3
3
  import { LastModifiedDownloader } from '@lde/distribution-downloader';
4
+ import { shellQuote } from '@lde/task-runner';
4
5
  import { basename, dirname, join } from 'path';
5
6
  import { readFile, stat, writeFile } from 'node:fs/promises';
6
7
  import { needsPreprocessing, preprocess } from './preprocess.js';
@@ -176,20 +177,6 @@ export class Importer {
176
177
  return await this.options.taskRunner.wait(indexTask);
177
178
  }
178
179
  }
179
- /**
180
- * POSIX-quote a value for safe interpolation into a shell command: wrap it in
181
- * single quotes and escape any embedded single quote as `'\''`.
182
- *
183
- * Without this, a data filename containing an apostrophe – e.g. a dataset
184
- * titled `'s-Hertogenbosch`, whose distribution URL maps to a local file like
185
- * `…Erfgoed+'s-Hertogenbosch.nt` – would terminate the surrounding quotes, so
186
- * `cat`/`gunzip` would read a non-existent path and feed `qlever-index` empty
187
- * input. The index then "succeeds" with 0 triples, the import is treated as
188
- * failed, and every distribution (and the JSON-LD fallback) fails the same way.
189
- */
190
- function shellQuote(value) {
191
- return `'${value.replace(/'/g, "'\\''")}'`;
192
- }
193
180
  /**
194
181
  * Native QLever index formats – `qlever-index -F <flag>` consumes these
195
182
  * directly. JSON-LD is not here: it is preprocessed to N-Quads first (see
@@ -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`) — including when wrapped in
16
- * `application/gzip` or `application/zip` — go straight through the shell
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
- * (declared `compressFormat` or `.gz` filename) and zip containers (folds
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
  */
@@ -1 +1 @@
1
- {"version":3,"file":"preprocess.d.ts","sourceRoot":"","sources":["../src/preprocess.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AA6B5C,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"}
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"}
@@ -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`) — including when wrapped in
33
- * `application/gzip` or `application/zip` — go straight through the shell
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
- * (declared `compressFormat` or `.gz` filename) and zip containers (folds
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, distribution);
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, distribution) {
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 = isGzipped ? source.pipe(createGunzip()) : source;
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.4",
3
+ "version": "0.15.6",
4
4
  "repository": {
5
5
  "url": "git+https://github.com/ldelements/lde.git",
6
6
  "directory": "packages/sparql-qlever"
@@ -26,11 +26,11 @@
26
26
  "dependencies": {
27
27
  "@lde/dataset": "^0.7.9",
28
28
  "@lde/distribution-downloader": "^0.6.8",
29
- "@lde/sparql-importer": "^0.6.9",
29
+ "@lde/sparql-importer": "^0.6.10",
30
30
  "@lde/sparql-server": "^0.4.14",
31
- "@lde/task-runner": "^0.2.13",
32
- "@lde/task-runner-docker": "^0.2.16",
33
- "@lde/task-runner-native": "^0.2.16",
31
+ "@lde/task-runner": "^0.2.14",
32
+ "@lde/task-runner-docker": "^0.2.17",
33
+ "@lde/task-runner-native": "^0.2.17",
34
34
  "@lde/wait-for-sparql": "^0.2.17",
35
35
  "rdf-parse": "^5.0.0",
36
36
  "rdf-serialize": "^5.1.0",