@lde/distribution-probe 0.2.3 → 0.2.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.
Files changed (2) hide show
  1. package/dist/probe.js +23 -11
  2. package/package.json +1 -1
package/dist/probe.js CHANGED
@@ -551,7 +551,7 @@ async function validateDumpBody(url, headers, options, headResponse) {
551
551
  const { text, truncated, corrupt } = await decodeProbeBody(bounded);
552
552
  return corrupt
553
553
  ? 'Distribution is not valid gzip'
554
- : await validateBody(text, getResponse.headers.get('Content-Type'), url, budgetMs, truncated);
554
+ : await validateBody(text, getResponse.headers.get('Content-Type'), url, budgetController.signal, truncated);
555
555
  })().catch(() => null);
556
556
  let budgetTimer;
557
557
  const budgetExpiry = new Promise((resolve) => {
@@ -697,7 +697,7 @@ const rdfContentTypes = [
697
697
  // validated if it happened to fit the read cap in full; beyond that it is
698
698
  // inconclusive, and we must not download it in full to find out.
699
699
  const nonStreamableRdfContentTypes = ['application/ld+json'];
700
- async function validateBody(body, contentType, baseIRI, timeoutMs, truncated) {
700
+ async function validateBody(body, contentType, baseIRI, budgetSignal, truncated) {
701
701
  if (body.length === 0) {
702
702
  // A complete, empty body is a faulty distribution; an empty *prefix* (a
703
703
  // truncated read that yielded no bytes, e.g. a corrupt gzip header) is
@@ -717,7 +717,7 @@ async function validateBody(body, contentType, baseIRI, timeoutMs, truncated) {
717
717
  // a complete document, small enough to fit the read cap, can be validated.
718
718
  return null;
719
719
  }
720
- const outcome = await classifyRdfBody(body, serialization, baseIRI, timeoutMs, truncated);
720
+ const outcome = await classifyRdfBody(body, serialization, baseIRI, budgetSignal, truncated);
721
721
  switch (outcome.type) {
722
722
  case 'empty':
723
723
  return 'Distribution contains no RDF triples';
@@ -734,33 +734,45 @@ async function validateBody(body, contentType, baseIRI, timeoutMs, truncated) {
734
734
  * Parse an RDF body just far enough to tell whether it carries any triples:
735
735
  * resolve on the first triple (presence is all we need, not a full count), on a
736
736
  * clean end with none ('empty'), or on a parse error. The parse is bounded by
737
- * `timeoutMs` because a JSON-LD `@context` is fetched from its origin, and a
738
- * slow or hanging context host would otherwise stall the probe past its budget;
739
- * on expiry — and likewise when a remote `@context` is unreachable — the outcome
740
- * is 'inconclusive', so a valid distribution is never flagged faulty for a
741
- * context host's failure. `baseIRI` resolves any relative IRIs in the document.
737
+ * `budgetSignal` – the caller's validation-budget {@link AbortController} –
738
+ * because a JSON-LD `@context` is fetched from its origin, and a slow or hanging
739
+ * context host would otherwise stall the probe past its budget; on abort – and
740
+ * likewise when a remote `@context` is unreachable – the outcome is
741
+ * 'inconclusive', so a valid distribution is never flagged faulty for a context
742
+ * host's failure. Sharing the caller's single budget (rather than starting a
743
+ * second, identical timer) keeps that timeout deterministic: it fires once, when
744
+ * the budget elapses, instead of leaving an orphan timer to settle on its own
745
+ * schedule. `baseIRI` resolves any relative IRIs in the document.
742
746
  *
743
747
  * When `truncated` is true the body is only a bounded prefix of a larger one, so
744
748
  * only finding a triple ('hasTriples') is conclusive: a parse error at the cut
745
749
  * or a clean end with no triple yet means we did not read far enough, not that
746
750
  * the distribution is empty or malformed, and is reported as 'inconclusive'.
747
751
  */
748
- function classifyRdfBody(body, contentType, baseIRI, timeoutMs, truncated) {
752
+ function classifyRdfBody(body, contentType, baseIRI, budgetSignal, truncated) {
749
753
  return new Promise((resolve) => {
750
754
  const quads = rdfParser.parse(Readable.from([body]), {
751
755
  contentType,
752
756
  baseIRI,
753
757
  });
754
- const timer = setTimeout(() => settle({ type: 'inconclusive' }), timeoutMs);
758
+ const onBudgetElapsed = () => settle({ type: 'inconclusive' });
755
759
  let settled = false;
756
760
  function settle(outcome) {
757
761
  if (settled)
758
762
  return;
759
763
  settled = true;
760
- clearTimeout(timer);
764
+ budgetSignal.removeEventListener('abort', onBudgetElapsed);
761
765
  quads.destroy();
762
766
  resolve(outcome);
763
767
  }
768
+ // The budget may already have elapsed while the body was being read; treat an
769
+ // already-aborted signal as an immediate expiry rather than waiting for an
770
+ // 'abort' event that will never come.
771
+ if (budgetSignal.aborted) {
772
+ onBudgetElapsed();
773
+ return;
774
+ }
775
+ budgetSignal.addEventListener('abort', onBudgetElapsed, { once: true });
764
776
  quads
765
777
  .on('data', () => settle({ type: 'hasTriples' }))
766
778
  .on('error', (error) => settle(truncated || isRemoteContextError(error)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lde/distribution-probe",
3
- "version": "0.2.3",
3
+ "version": "0.2.4",
4
4
  "repository": {
5
5
  "url": "git+https://github.com/ldelements/lde.git",
6
6
  "directory": "packages/distribution-probe"