@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 +3 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2 -0
- package/dist/usability.d.ts +35 -0
- package/dist/usability.d.ts.map +1 -0
- package/dist/usability.js +43 -0
- package/dist/verdict.d.ts +38 -0
- package/dist/verdict.d.ts.map +1 -0
- package/dist/verdict.js +96 -0
- package/package.json +2 -2
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
|
+
|
package/dist/index.d.ts
ADDED
|
@@ -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,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"}
|
package/dist/verdict.js
ADDED
|
@@ -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.
|
|
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.
|
|
27
|
+
"@lde/distribution-probe": "0.1.11",
|
|
28
28
|
"@lde/sparql-importer": "0.6.5",
|
|
29
29
|
"tslib": "^2.3.0"
|
|
30
30
|
},
|