@lde/distribution-probe 0.4.0 → 0.5.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/probe.d.ts +3 -1
- package/dist/probe.d.ts.map +1 -1
- package/dist/probe.js +49 -6
- package/package.json +3 -3
package/dist/probe.d.ts
CHANGED
|
@@ -143,7 +143,9 @@ export type ProbeResultType = SparqlProbeResult | DataDumpProbeResult | NetworkE
|
|
|
143
143
|
* For SPARQL endpoints, issues the configured SPARQL query (default: a
|
|
144
144
|
* minimal `SELECT`). For data dumps, issues `HEAD` (with a `GET` fallback
|
|
145
145
|
* for small or unknown-size bodies, reading only a bounded prefix so a large
|
|
146
|
-
* streamed dump is never downloaded in full).
|
|
146
|
+
* streamed dump is never downloaded in full). A
|
|
147
|
+
* {@link Distribution.localFile | local file} is probed the same way, answered
|
|
148
|
+
* from the filesystem; a missing one is a {@link NetworkError}.
|
|
147
149
|
*
|
|
148
150
|
* Returns a pure result object; never throws.
|
|
149
151
|
*/
|
package/dist/probe.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"probe.d.ts","sourceRoot":"","sources":["../src/probe.ts"],"names":[],"mappings":"AAKA,OAAO,EAAyB,YAAY,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"probe.d.ts","sourceRoot":"","sources":["../src/probe.ts"],"names":[],"mappings":"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"}
|
package/dist/probe.js
CHANGED
|
@@ -2,6 +2,8 @@ import { hostKeyOf, mapHostLimited, positiveIntOrDefault, } from '@lde/host-limi
|
|
|
2
2
|
import { compressionMediaTypes } from '@lde/dataset';
|
|
3
3
|
import { rdfParser } from 'rdf-parse';
|
|
4
4
|
import { FetchDocumentLoader, } from 'jsonld-context-parser';
|
|
5
|
+
import { constants, createReadStream } from 'node:fs';
|
|
6
|
+
import { access, stat } from 'node:fs/promises';
|
|
5
7
|
import { Readable } from 'node:stream';
|
|
6
8
|
import { createGunzip } from 'node:zlib';
|
|
7
9
|
const DEFAULT_SPARQL_QUERY = 'SELECT * { ?s ?p ?o } LIMIT 1';
|
|
@@ -133,7 +135,9 @@ export class DataDumpProbeResult extends ProbeResult {
|
|
|
133
135
|
* For SPARQL endpoints, issues the configured SPARQL query (default: a
|
|
134
136
|
* minimal `SELECT`). For data dumps, issues `HEAD` (with a `GET` fallback
|
|
135
137
|
* for small or unknown-size bodies, reading only a bounded prefix so a large
|
|
136
|
-
* streamed dump is never downloaded in full).
|
|
138
|
+
* streamed dump is never downloaded in full). A
|
|
139
|
+
* {@link Distribution.localFile | local file} is probed the same way, answered
|
|
140
|
+
* from the filesystem; a missing one is a {@link NetworkError}.
|
|
137
141
|
*
|
|
138
142
|
* Returns a pure result object; never throws.
|
|
139
143
|
*/
|
|
@@ -414,7 +418,8 @@ async function probeDataDump(url, distribution, options, authHeaders, start) {
|
|
|
414
418
|
signal: AbortSignal.timeout(options.timeoutMs),
|
|
415
419
|
headers,
|
|
416
420
|
};
|
|
417
|
-
const
|
|
421
|
+
const fetchDump = dumpFetcher(distribution);
|
|
422
|
+
const headResponse = await fetchDump(url, {
|
|
418
423
|
method: 'HEAD',
|
|
419
424
|
...requestOptions,
|
|
420
425
|
});
|
|
@@ -425,7 +430,7 @@ async function probeDataDump(url, distribution, options, authHeaders, start) {
|
|
|
425
430
|
if (options.validateRdfContent &&
|
|
426
431
|
isDeclaredRdf(distribution) &&
|
|
427
432
|
isHttpSuccess(headResponse)) {
|
|
428
|
-
const { response, failureReason } = await validateDumpBody(url, headers, options, headResponse);
|
|
433
|
+
const { response, failureReason } = await validateDumpBody(url, headers, options, headResponse, fetchDump);
|
|
429
434
|
return finalizeDataDump(url, distribution, response, start, failureReason);
|
|
430
435
|
}
|
|
431
436
|
// Reachability only. A successful HEAD is enough; otherwise confirm with a
|
|
@@ -433,10 +438,48 @@ async function probeDataDump(url, distribution, options, authHeaders, start) {
|
|
|
433
438
|
if (isHttpSuccess(headResponse)) {
|
|
434
439
|
return finalizeDataDump(url, distribution, headResponse, start, null);
|
|
435
440
|
}
|
|
436
|
-
const getResponse = await
|
|
441
|
+
const getResponse = await fetchDump(url, {
|
|
442
|
+
method: 'GET',
|
|
443
|
+
...requestOptions,
|
|
444
|
+
});
|
|
437
445
|
await getResponse.body?.cancel();
|
|
438
446
|
return finalizeDataDump(url, distribution, getResponse, start, null);
|
|
439
447
|
}
|
|
448
|
+
/**
|
|
449
|
+
* How to request a data dump: over HTTP, or for a local file from the filesystem
|
|
450
|
+
* in the shape of the HTTP response a static file server would give, so that
|
|
451
|
+
* reachability, content validation and the change fingerprint need no second
|
|
452
|
+
* path.
|
|
453
|
+
*/
|
|
454
|
+
function dumpFetcher(distribution) {
|
|
455
|
+
const { localPath } = distribution;
|
|
456
|
+
if (localPath === undefined) {
|
|
457
|
+
return fetch;
|
|
458
|
+
}
|
|
459
|
+
return async (_url, init) => {
|
|
460
|
+
const stats = await stat(localPath);
|
|
461
|
+
if (!stats.isFile()) {
|
|
462
|
+
throw new Error(`${localPath} is not a file`);
|
|
463
|
+
}
|
|
464
|
+
// An unreadable file would otherwise pass here and fail only at import,
|
|
465
|
+
// recorded against a fingerprint that a later chmod does not change.
|
|
466
|
+
await access(localPath, constants.R_OK);
|
|
467
|
+
return new Response(init.method === 'HEAD'
|
|
468
|
+
? null
|
|
469
|
+
: Readable.toWeb(createReadStream(localPath)), {
|
|
470
|
+
headers: {
|
|
471
|
+
'Content-Length': stats.size.toString(),
|
|
472
|
+
'Last-Modified': stats.mtime.toUTCString(),
|
|
473
|
+
// The RDF type for gzip too: the body is inflated by its magic bytes
|
|
474
|
+
// before validation. A zip keeps its own type, as it holds entries.
|
|
475
|
+
// Distribution.localFile always derives a media type.
|
|
476
|
+
'Content-Type': (distribution.compressMimeType === 'application/zip'
|
|
477
|
+
? distribution.compressMimeType
|
|
478
|
+
: distribution.mimeType),
|
|
479
|
+
},
|
|
480
|
+
});
|
|
481
|
+
};
|
|
482
|
+
}
|
|
440
483
|
/** Whether an HTTP response carries a success (2xx/3xx) status. */
|
|
441
484
|
function isHttpSuccess(response) {
|
|
442
485
|
return response.status >= 200 && response.status < 400;
|
|
@@ -461,14 +504,14 @@ function finalizeDataDump(url, distribution, response, start, failureReason) {
|
|
|
461
504
|
* unvalidated), never a failure. Returns the response to draw metadata from
|
|
462
505
|
* (the GET, or the HEAD when the GET could not start) alongside that reason.
|
|
463
506
|
*/
|
|
464
|
-
async function validateDumpBody(url, headers, options, headResponse) {
|
|
507
|
+
async function validateDumpBody(url, headers, options, headResponse, fetchDump) {
|
|
465
508
|
const budgetMs = Math.min(options.rdfValidationBudgetMs, options.timeoutMs);
|
|
466
509
|
// Aborting on budget expiry stops a slow endpoint from streaming on in the
|
|
467
510
|
// background once we have given up waiting for a triple.
|
|
468
511
|
const budgetController = new AbortController();
|
|
469
512
|
let getResponse;
|
|
470
513
|
try {
|
|
471
|
-
getResponse = await
|
|
514
|
+
getResponse = await fetchDump(url, {
|
|
472
515
|
method: 'GET',
|
|
473
516
|
headers,
|
|
474
517
|
signal: AbortSignal.any([
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lde/distribution-probe",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"repository": {
|
|
5
5
|
"url": "git+https://github.com/ldelements/lde.git",
|
|
6
6
|
"directory": "packages/distribution-probe"
|
|
@@ -24,8 +24,8 @@
|
|
|
24
24
|
"!**/*.tsbuildinfo"
|
|
25
25
|
],
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"@lde/dataset": "^0.
|
|
28
|
-
"@lde/host-limiter": "^0.2.
|
|
27
|
+
"@lde/dataset": "^0.10.0",
|
|
28
|
+
"@lde/host-limiter": "^0.2.1",
|
|
29
29
|
"jsonld-context-parser": "^2.4.0",
|
|
30
30
|
"rdf-parse": "^5.0.0",
|
|
31
31
|
"tslib": "^2.3.0"
|