@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 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
  */
@@ -1 +1 @@
1
- {"version":3,"file":"probe.d.ts","sourceRoot":"","sources":["../src/probe.ts"],"names":[],"mappings":"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"}
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 headResponse = await fetch(url, {
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 fetch(url, { method: 'GET', ...requestOptions });
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 fetch(url, {
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.4.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.9.0",
28
- "@lde/host-limiter": "^0.2.0",
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"