| Safe Haskell | None |
|---|---|
| Language | GHC2021 |
Ecluse.Registry.Npm.Project
Contents
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.Package —
PackageInfo (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'shasInstallScriptflag istrueor thescriptsmap declares any ofpreinstall/install/postinstall(matching what npm itself sets the flag from). The two fields are independent on the wire, so thescriptsmap is consulted __even whenhasInstallScriptis present andfalse__: 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 toNoCodeOnInstall(both metadata forms always carry thescripts/hasInstallScriptinformation, so its absence is a determination, not an unknown). deprecated→Availability: a notice yieldsDeprecated(carrying the message), its absenceAvailable. npm has no per-version yank, soYankednever arises here.dist→ a single-elementNonEmptyofArtifact(npm publishes exactly one tarball per version). Both integrity digests survive when present and well-formed:dist.shasumas aSHA1Hashanddist.integrityas anSRIHash. 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 validatingmkHash, so a malformed one — empty ("shasum":""/"integrity":""), truncated, non-hex, or bad-base64 — is unconstructable and so treated as absent, never as a degenerateHash: a digest that ties the version to no tamper-evident fingerprint must not slip past the public-integrity admission gate._npmUser→pkgPublisher(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'stimemap, not the manifest; a version with notimeentry (or an abbreviated document, which omitstime) projects toNothing.
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
- parsePackageInfo :: PackageName -> RegistryResponse -> Either ParseError PackageInfo
- parsePackageInfoFromValue :: PackageName -> Value -> Either ParseError Projection
- parseVersionDetails :: RegistryResponse -> Version -> Either ParseError PackageDetails
- parseVersionList :: RegistryResponse -> Either ParseError [Version]
- data Projection
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
| Show Projection Source # | |
Defined in Ecluse.Registry.Npm.Project Methods showsPrec :: Int -> Projection -> ShowS # show :: Projection -> String # showList :: [Projection] -> ShowS # | |
| Eq Projection Source # | |
Defined in Ecluse.Registry.Npm.Project | |