ecluse
Safe HaskellNone
LanguageGHC2021

Ecluse.Registry.Npm.Project

Description

Projection of npm wire JSON into the ecosystem-agnostic domain model.

This module is the second half of the npm protocol boundary. Where Ecluse.Registry.Npm.Wire captures what the registry said as faithful wire types, this module turns those into the domain vocabulary of Ecluse.PackagePackageInfo (the packument-level view) and PackageDetails (the per-version snapshot the rules engine evaluates). Together they realise the parse* fields of the Ecluse.Registry handle: nothing above the adapter ever sees npm wire data.

The projection is pure and total (it returns Either ParseError, never throws), the execution half of parse, don't validate — once a response has been projected, downstream code holds precise domain types and never re-inspects the wire shape.

Signal mapping

The npm-specific fields collapse onto the normalised, ecosystem-blind signals:

  • install-script presence → CodeExecSignal, read fail-closed across two independent wire signals. A version runs code on install when either the abbreviated form's hasInstallScript flag is true or the scripts map declares any of preinstall/install/postinstall (matching what npm itself sets the flag from). The two fields are independent on the wire, so the scripts map is consulted __even when hasInstallScript is present and false__: a hostile upstream must not be able to mask a real install hook by lying in the sibling flag, so a declared script is authoritative and the signal is the union of the two, never the flag overriding a script. A version with neither signal maps to NoCodeOnInstall (both metadata forms always carry the scripts/hasInstallScript information, so its absence is a determination, not an unknown).
  • deprecatedAvailability: a notice yields Deprecated (carrying the message), its absence Available. npm has no per-version yank, so Yanked never arises here.
  • dist → a single-element NonEmpty of Artifact (npm publishes exactly one tarball per version). Both integrity digests survive when present and well-formed: dist.shasum as a SHA1 Hash and dist.integrity as an SRI Hash. Carrying both is load-bearing — a cross-upstream merge compares the same version's integrity across the private and public registries to detect a supply-chain divergence, which dropping either digest would blind. Each digest is built through the validating mkHash, so a malformed one — empty ("shasum":"" / "integrity":""), truncated, non-hex, or bad-base64 — is unconstructable and so treated as absent, never as a degenerate Hash: a digest that ties the version to no tamper-evident fingerprint must not slip past the public-integrity admission gate.
  • _npmUserpkgPublisher (who pushed this version — provenance). It rides on the version object but is not modelled by the wire manifest, so the projection reads it directly from the version object here.
  • time[version]pkgPublishedAt. The publish timestamp lives in the packument's time map, not the manifest; a version with no time entry (or an abbreviated document, which omits time) projects to Nothing.

Trust is left TrustUnknown: establishing it needs signature verification against npm's published keys, a fetch this pure projection does not perform.

Name as a validation input

The requested PackageName — the identity the proxy resolved from the route — is the validation authority for the served packument's name, never a rewrite of it. The packument projection takes the requested name and checks the upstream's self-reported top-level name against it: a document whose self-report agrees is a Projected PackageInfo carrying the name the upstream genuinely reported; a document whose self-report disagrees is a NameMismatch, so the caller can treat that origin as untrusted for this request and drop its contribution. The served name is therefore always a value an upstream genuinely reported, never a substituted or manufactured one. An absent or otherwise undecodable name remains a ParseError, as before — distinct from a present-but-different name.

Synopsis

Projection

parsePackageInfo :: PackageName -> RegistryResponse -> Either ParseError PackageInfo Source #

Project a fetched metadata response into the packument-level PackageInfo for the requested package. Pure and total: a body that is not a decodable npm packument is reported as a ParseError, never thrown.

The requested name is the validation authority. A document whose self-reported name disagrees with the request cannot yield a valid view of the requested package, so it is reported as a ParseError here — the typed-view accessor admits only a matching document. The finer Projection (a mismatch distinguished from a decode failure) is surfaced by parsePackageInfoFromValue, which the serve layer uses to distinguish a misreporting origin from an undecodable one.

parsePackageInfoFromValue :: PackageName -> Value -> Either ParseError Projection Source #

Project an already-decoded packument Value into a Projection for the requested package, without re-parsing any bytes. This is the entry point the serve layer uses when it has already decoded the upstream body to a raw Value (the document it edits in place to serve) and wants the typed view of the same document: projecting from the Value reuses that one parse rather than tokenising the bytes a second time. Pure and total — a Value that is not a decodable npm packument is reported as a ParseError, never thrown.

The requested name validates the self-reported name: a match is Projected, a disagreement is NameMismatch. The serve layer drops a NameMismatch origin's contribution (an untrusted, misreporting upstream) and keeps the served name a value some upstream genuinely reported.

parseVersionDetails :: RegistryResponse -> Version -> Either ParseError PackageDetails Source #

Project a fetched metadata response into the PackageDetails for a single version. Fails with a ParseError if the body does not decode or the requested version is absent from the packument.

parseVersionList :: RegistryResponse -> Either ParseError [Version] Source #

Extract the list of available versions from a fetched metadata response, in the packument's versions key order. Fails with a ParseError only if the body does not decode.

Name validation

data Projection Source #

The outcome of projecting an upstream packument against the requested package name (see the module header, "Name as a validation input").

The requested name validates the document; it never rewrites it. A document whose self-reported name agrees with the request is Projected; one that disagrees is a NameMismatch. The PackageInfo of a Projected carries the name the upstream genuinely reported (which, having matched, equals the requested name) — never a substituted value.

Constructors

Projected PackageInfo

The document decoded and its self-reported name matched the request.

NameMismatch Text

The document decoded but self-reported this different name (carried verbatim for the audit log).

Instances

Instances details
Show Projection Source # 
Instance details

Defined in Ecluse.Registry.Npm.Project

Eq Projection Source # 
Instance details

Defined in Ecluse.Registry.Npm.Project