@lde/distribution-health 0.1.1 → 0.1.3

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 CHANGED
@@ -32,3 +32,6 @@ into a SKOS failure scheme (`<scheme>#${reason}`) with no lookup table.
32
32
 
33
33
  The normative usability rule and the rejected alternatives are recorded in the
34
34
  PRD: netwerk-digitaal-erfgoed/dataset-register#2103.
35
+
36
+ <!-- 0.1.1 was published to npm without its dist/; this republishes the package with the build output. -->
37
+
@@ -0,0 +1,3 @@
1
+ export { importOutcomeToVerdict, probeResultToVerdict, type ValidityVerdict, type ValidityFailureReason, type ValidityDepth, } from './verdict.js';
2
+ export { usability, type Reachability, type Usability, type UsabilityState, type UsabilityCause, } from './usability.js';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,sBAAsB,EACtB,oBAAoB,EACpB,KAAK,eAAe,EACpB,KAAK,qBAAqB,EAC1B,KAAK,aAAa,GACnB,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,SAAS,EACT,KAAK,YAAY,EACjB,KAAK,SAAS,EACd,KAAK,cAAc,EACnB,KAAK,cAAc,GACpB,MAAM,gBAAgB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ export { importOutcomeToVerdict, probeResultToVerdict, } from './verdict.js';
2
+ export { usability, } from './usability.js';
@@ -0,0 +1,35 @@
1
+ import type { ValidityVerdict } from './verdict.js';
2
+ /** The single derived health verdict consumers act on. */
3
+ export type UsabilityState = 'usable' | 'unusable' | 'unknown';
4
+ /**
5
+ * Why a distribution is not plainly `usable`. Carried separately from the
6
+ * state so a consumer can render the reason without re-deriving it.
7
+ */
8
+ export type UsabilityCause = 'invalid' | 'unreachable' | 'no-verdict' | 'stale-verdict';
9
+ /** The currently-observed reachability of a distribution. */
10
+ export interface Reachability {
11
+ /** Whether the distribution could be fetched (HTTP/SPARQL level). */
12
+ reachable: boolean;
13
+ /**
14
+ * The source fingerprint observed on this reachability check – the shared
15
+ * key against which a validity verdict’s `validatedFingerprint` is matched.
16
+ */
17
+ fingerprint: string | null;
18
+ }
19
+ /** Result of the usability rollup. */
20
+ export interface Usability {
21
+ state: UsabilityState;
22
+ cause?: UsabilityCause;
23
+ /**
24
+ * Set when the state rests on a shallow verdict only (no deep verdict
25
+ * applied), so a consumer can mark the result as not yet deeply confirmed.
26
+ */
27
+ shallow?: true;
28
+ }
29
+ /**
30
+ * Roll reachability and validity verdict(s) up into the one canonical
31
+ * usability verdict. Reachability dominates: an unreachable distribution is
32
+ * unusable regardless of any validity verdict.
33
+ */
34
+ export declare function usability(reachability: Reachability, verdicts: readonly ValidityVerdict[]): Usability;
35
+ //# sourceMappingURL=usability.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"usability.d.ts","sourceRoot":"","sources":["../src/usability.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAEpD,0DAA0D;AAC1D,MAAM,MAAM,cAAc,GAAG,QAAQ,GAAG,UAAU,GAAG,SAAS,CAAC;AAE/D;;;GAGG;AACH,MAAM,MAAM,cAAc,GACtB,SAAS,GACT,aAAa,GACb,YAAY,GACZ,eAAe,CAAC;AAEpB,6DAA6D;AAC7D,MAAM,WAAW,YAAY;IAC3B,qEAAqE;IACrE,SAAS,EAAE,OAAO,CAAC;IACnB;;;OAGG;IACH,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B;AAED,sCAAsC;AACtC,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,cAAc,CAAC;IACtB,KAAK,CAAC,EAAE,cAAc,CAAC;IACvB;;;OAGG;IACH,OAAO,CAAC,EAAE,IAAI,CAAC;CAChB;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CACvB,YAAY,EAAE,YAAY,EAC1B,QAAQ,EAAE,SAAS,eAAe,EAAE,GACnC,SAAS,CAkBX"}
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Roll reachability and validity verdict(s) up into the one canonical
3
+ * usability verdict. Reachability dominates: an unreachable distribution is
4
+ * unusable regardless of any validity verdict.
5
+ */
6
+ export function usability(reachability, verdicts) {
7
+ if (!reachability.reachable) {
8
+ return { state: 'unusable', cause: 'unreachable' };
9
+ }
10
+ const chosen = chooseVerdict(verdicts, reachability.fingerprint);
11
+ if (chosen === undefined) {
12
+ // A verdict that exists but no longer matches the observed fingerprint has
13
+ // gone stale (e.g. a since-fixed dump); distinguish that from never having
14
+ // been judged at all, so a stale negative stops showing as broken.
15
+ const cause = verdicts.length > 0 ? 'stale-verdict' : 'no-verdict';
16
+ return { state: 'unknown', cause };
17
+ }
18
+ const flag = chosen.depth === 'shallow' ? { shallow: true } : {};
19
+ return chosen.valid
20
+ ? { state: 'usable', ...flag }
21
+ : { state: 'unusable', cause: 'invalid', ...flag };
22
+ }
23
+ /**
24
+ * The authoritative verdict among those still fresh against the
25
+ * currently-observed fingerprint, or `undefined` when none apply. A deep
26
+ * verdict beats a shallow one; freshness is decided by the staleness gate.
27
+ */
28
+ function chooseVerdict(verdicts, currentFingerprint) {
29
+ const fresh = verdicts.filter((verdict) => isFresh(verdict, currentFingerprint));
30
+ return fresh.find((verdict) => verdict.depth === 'deep') ?? fresh[0];
31
+ }
32
+ /**
33
+ * Whether a verdict still applies to the currently-observed source. A verdict
34
+ * applies only if the fingerprint it was judged against equals the current
35
+ * one; a `null` fingerprint never compares equal (mirroring
36
+ * `sourceFingerprint`), so an unfingerprintable source is never considered
37
+ * fresh.
38
+ */
39
+ function isFresh(verdict, currentFingerprint) {
40
+ return (currentFingerprint !== null &&
41
+ verdict.validatedFingerprint !== null &&
42
+ verdict.validatedFingerprint === currentFingerprint);
43
+ }
@@ -0,0 +1,38 @@
1
+ import { ImportFailed, ImportSuccessful } from '@lde/sparql-importer';
2
+ import { type ProbeResultType } from '@lde/distribution-probe';
3
+ /**
4
+ * Why a distribution’s RDF was judged invalid. The local names are chosen to
5
+ * map 1:1 onto a consumer’s SKOS failure scheme (`<scheme>#${reason}`, no lookup
6
+ * table), mirroring how `@lde/iiif-validator`’s reason enum maps to NDE’s
7
+ * `manifest-validation-failure#`.
8
+ */
9
+ export type ValidityFailureReason = 'parse-error' | 'empty';
10
+ /** How thoroughly validity was assessed. */
11
+ export type ValidityDepth = 'shallow' | 'deep';
12
+ /**
13
+ * A verdict on whether a distribution’s fetched content parses as RDF.
14
+ *
15
+ * `reason` and `message` are present only when `valid` is `false`. The verdict
16
+ * carries the `validatedFingerprint` it was judged against so a consumer can
17
+ * tell whether it still applies to the currently-observed source.
18
+ */
19
+ export interface ValidityVerdict {
20
+ valid: boolean;
21
+ reason?: ValidityFailureReason;
22
+ message?: string;
23
+ validatedFingerprint: string | null;
24
+ depth: ValidityDepth;
25
+ }
26
+ /**
27
+ * Map a deep import outcome (full parse) to a validity verdict. A failed import
28
+ * is invalid RDF; a successful one is valid.
29
+ */
30
+ export declare function importOutcomeToVerdict(outcome: ImportFailed | ImportSuccessful, validatedFingerprint: string | null): ValidityVerdict;
31
+ /**
32
+ * Map a shallow probe result to a validity verdict, or `null` when the probe
33
+ * carries no validity signal (a network or HTTP-level failure, or a body the
34
+ * probe did not parse). This is the shallow producer: it interprets the body
35
+ * validation `@lde/distribution-probe` already performs for small RDF dumps.
36
+ */
37
+ export declare function probeResultToVerdict(result: ProbeResultType, validatedFingerprint: string | null): ValidityVerdict | null;
38
+ //# sourceMappingURL=verdict.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"verdict.d.ts","sourceRoot":"","sources":["../src/verdict.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AACtE,OAAO,EAEL,KAAK,eAAe,EACrB,MAAM,yBAAyB,CAAC;AAEjC;;;;;GAKG;AACH,MAAM,MAAM,qBAAqB,GAAG,aAAa,GAAG,OAAO,CAAC;AAE5D,4CAA4C;AAC5C,MAAM,MAAM,aAAa,GAAG,SAAS,GAAG,MAAM,CAAC;AAE/C;;;;;;GAMG;AACH,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,CAAC,EAAE,qBAAqB,CAAC;IAC/B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,oBAAoB,EAAE,MAAM,GAAG,IAAI,CAAC;IACpC,KAAK,EAAE,aAAa,CAAC;CACtB;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,YAAY,GAAG,gBAAgB,EACxC,oBAAoB,EAAE,MAAM,GAAG,IAAI,GAClC,eAAe,CAoBjB;AA6BD;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,eAAe,EACvB,oBAAoB,EAAE,MAAM,GAAG,IAAI,GAClC,eAAe,GAAG,IAAI,CA4BxB"}
@@ -0,0 +1,96 @@
1
+ import { ImportSuccessful } from '@lde/sparql-importer';
2
+ import { DataDumpProbeResult, } from '@lde/distribution-probe';
3
+ /**
4
+ * Map a deep import outcome (full parse) to a validity verdict. A failed import
5
+ * is invalid RDF; a successful one is valid.
6
+ */
7
+ export function importOutcomeToVerdict(outcome, validatedFingerprint) {
8
+ if (outcome instanceof ImportSuccessful) {
9
+ if (outcome.tripleCount === 0) {
10
+ return {
11
+ valid: false,
12
+ reason: 'empty',
13
+ validatedFingerprint,
14
+ depth: 'deep',
15
+ };
16
+ }
17
+ return { valid: true, validatedFingerprint, depth: 'deep' };
18
+ }
19
+ return {
20
+ valid: false,
21
+ reason: 'parse-error',
22
+ message: outcome.error,
23
+ validatedFingerprint,
24
+ depth: 'deep',
25
+ };
26
+ }
27
+ // The probe’s `failureReason` strings that signal an empty distribution rather
28
+ // than a parse error. Mirrors `validateBody` in `@lde/distribution-probe`.
29
+ const EMPTY_FAILURE_REASONS = new Set([
30
+ 'Distribution is empty',
31
+ 'Distribution contains no RDF triples',
32
+ ]);
33
+ // Content types `@lde/distribution-probe` parse-validates; a successful probe
34
+ // of one of these is positive evidence the body parsed. Mirrors
35
+ // `rdfContentTypes` there. (Duplicated rather than imported because the probe
36
+ // does not export it; a structured validity signal on the probe result would
37
+ // remove this coupling — see #468.)
38
+ const PROBE_PARSED_CONTENT_TYPES = new Set([
39
+ 'text/turtle',
40
+ 'application/n-triples',
41
+ 'application/n-quads',
42
+ 'application/trig',
43
+ 'text/n3',
44
+ 'application/ld+json',
45
+ 'application/rdf+xml',
46
+ ]);
47
+ // The probe only fetches and parses bodies at or below this size; larger ones
48
+ // are HEAD-checked only, so a success carries no validity signal. Mirrors the
49
+ // threshold in `probeDataDump`.
50
+ const PROBE_PARSE_LIMIT_BYTES = 10_240;
51
+ /**
52
+ * Map a shallow probe result to a validity verdict, or `null` when the probe
53
+ * carries no validity signal (a network or HTTP-level failure, or a body the
54
+ * probe did not parse). This is the shallow producer: it interprets the body
55
+ * validation `@lde/distribution-probe` already performs for small RDF dumps.
56
+ */
57
+ export function probeResultToVerdict(result, validatedFingerprint) {
58
+ if (!(result instanceof DataDumpProbeResult)) {
59
+ return null;
60
+ }
61
+ if (result.failureReason !== null) {
62
+ if (EMPTY_FAILURE_REASONS.has(result.failureReason)) {
63
+ return {
64
+ valid: false,
65
+ reason: 'empty',
66
+ validatedFingerprint,
67
+ depth: 'shallow',
68
+ };
69
+ }
70
+ return {
71
+ valid: false,
72
+ reason: 'parse-error',
73
+ message: result.failureReason,
74
+ validatedFingerprint,
75
+ depth: 'shallow',
76
+ };
77
+ }
78
+ if (result.isSuccess() && probeParsedBody(result)) {
79
+ return { valid: true, validatedFingerprint, depth: 'shallow' };
80
+ }
81
+ return null;
82
+ }
83
+ /**
84
+ * Whether the probe actually parsed this distribution’s body – the only
85
+ * grounds on which a `valid: true` shallow verdict may rest. True when the
86
+ * content type is one the probe parse-validates and the body was small enough
87
+ * to have been fetched rather than only HEAD-checked.
88
+ */
89
+ function probeParsedBody(result) {
90
+ const serialization = result.contentType?.split(';')[0].trim();
91
+ if (serialization === undefined ||
92
+ !PROBE_PARSED_CONTENT_TYPES.has(serialization)) {
93
+ return false;
94
+ }
95
+ return (result.contentSize === null || result.contentSize <= PROBE_PARSE_LIMIT_BYTES);
96
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lde/distribution-health",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "repository": {
5
5
  "url": "git+https://github.com/ldelements/lde.git",
6
6
  "directory": "packages/distribution-health"
@@ -24,7 +24,7 @@
24
24
  "!**/*.tsbuildinfo"
25
25
  ],
26
26
  "dependencies": {
27
- "@lde/distribution-probe": "0.1.10",
27
+ "@lde/distribution-probe": "0.1.11",
28
28
  "@lde/sparql-importer": "0.6.5",
29
29
  "tslib": "^2.3.0"
30
30
  },