ecluse:ecluse-core
Safe HaskellNone
LanguageGHC2021

Ecluse.Core.Server.Context

Description

The per-request context the serve path reads through, and the handler monad over it.

Mount dispatch matches a request to one MountBinding -- a mount's complete ecosystem wiring -- then runs the route's handler in Handler, a reader over a RequestCtx pairing that binding with the request runtime ServeRuntime. A handler reads its per-mount dependencies (the classifier, the packument-serve dependencies, the error renderer, the path prefix) and the shared runtime from that one context, rather than taking them as explicit arguments threaded down the pipeline.

ServeRuntime is the runtime interface the serve path is closed over: the two data-plane HTTP managers, the metadata cache, the mirror queue, and the abstract metric- and tracing-recording ports. It holds precisely what the pipeline needs to serve a request and nothing more; the application's composition root constructs it (wiring the concrete OpenTelemetry-backed ports), and a test constructs it over doubles. Logging is not a field: a handler logs through the ambient katip context, which the dispatch boundary establishes (with the structured-log scribes and the trace-correlation dd object) when it runs the handler.

RequestCtx is a concrete record with plain accessors (ctxRuntime, ctxMount). The handler monad layers over katip's logging context, so a structured log call composes uniformly across the serve path.

Synopsis

Request runtime

data ServeRuntime Source #

The runtime backends the serve path is closed over: exactly the effectful capabilities a request needs to fetch, gate, serve, and record. A record of concrete handles and abstract ports (the Handle pattern), assembled by the composition root and read by every handler through the RequestCtx.

The two HTTP managers carry the per-origin split: the public manager serves the untrusted public-upstream and artifact egress, the private manager the trusted private-upstream path. Both are the validating TLS manager (registry egress is https-only by construction; certificate validation authenticates the host), so the split is in credential handling and the dist.tarball host gate's trust, not the manager. The metadata cache and mirror queue are the shared data-plane handles. The metric and tracing ports are the abstract recording interfaces (Ecluse.Core.Telemetry.Record, Ecluse.Core.Telemetry.Span); the application supplies their OpenTelemetry-backed implementations, so the serve path records without naming a telemetry backend. There is no log field: handlers log through the ambient katip context.

Constructors

ServeRuntime 

Fields

  • srAdmission :: ServeAdmission

    The process-wide brief-wait bound around metadata materialisation (Ecluse.Core.Server.Admission). A private tarball hit and the artifact streaming pump stay outside it; packument work and a tarball miss's public metadata gate acquire a slot, waiting briefly for one under load.

  • srPublicManager :: Manager

    The validating-TLS data-plane manager for the untrusted public-upstream metadata fetch and every artifact stream.

  • srPrivateManager :: Manager

    The manager for the trusted private upstream. The same validating TLS manager; the private origin differs in credential handling, not in the manager.

  • srMetadataCache :: MetadataCache

    The short-TTL, size-bounded metadata cache shared by the serve paths (see Ecluse.Core.Server.Cache).

  • srQueue :: MirrorQueue

    The mirror-queue handle: the durable, best-effort hand-off from the serve path to the mirror worker.

  • srMetrics :: MetricsPort

    The metric-recording port the serve path emits the ecluse.* catalogue through.

  • srTracing :: TracingPort

    The tracing port the serve path opens its hand-added domain spans through.

Packument-serve dependencies

data PackumentDeps Source #

The per-mount inputs the serve handlers need beyond the request runtime ServeRuntime: the two upstream endpoints, the mount's externally-visible base URL, the mirror-target endpoint, its resolved rule policy, the edge auth token, the wall-clock source, and the operator help message.

These are a mount-level concern, resolved at the composition root (a separate concern) and carried on the mount's MountBinding; a handler reads exactly what it needs to decide and serve from the RequestCtx it runs in. Both the packument and the tarball paths share these deps -- the tarball path additionally gates one version and enqueues a mirror job to pdMirrorTarget -- so the name is retained for continuity rather than narrowed to one route.

Constructors

PackumentDeps 

Fields

Publish-serve dependencies

data PublishDeps Source #

The per-mount inputs the first-party publish handler needs: the publication target endpoint, the publish-scope allow-list (the anti-shadowing guard), the optional static fallback credential, the edge token, the response-bound budget, and the operator help message.

The mere presence of these deps is the publish path's opt-in: a mount carries a PublishDeps only when a publication target is configured, so the binding's bindingPublishDeps being Nothing is exactly the "no publication target ⇒ a PUT /{pkg} is 405 Method Not Allowed" rule, modelled in the type rather than re-derived at the handler (see docs/architecture/registry-model.md → "Publishing first-party packages").

The credential posture is passthrough, symmetric with the private-upstream read under passthrough: the publisher's own forwarded token is what reaches the publication target, the static pubStaticToken only a fallback for a client that sends none. Écluse mints no token of its own here -- unlike the mirror target -- so this record carries no CredentialProvider (see docs/architecture/access-model.md → "Publishing: the publication target").

Constructors

PublishDeps 

Fields

  • pubTargetUrl :: Text

    The publication target endpoint (ECLUSE_PUBLICATION_TARGET) a client npm publish is relayed to. The package path is appended to it.

  • pubScopes :: [Scope]

    The configured publish-scope allow-list (ECLUSE_PUBLISH_SCOPES) -- the anti-shadowing guard. A publish whose package name is not within one of these scopes is refused before any upstream write, so a client cannot publish a name that shadows an existing public package (a dependency-confusion vector). Never empty when a publication target is configured (config validation rejects that).

  • pubStaticToken :: Maybe Secret

    The static fallback credential (ECLUSE_PUBLICATION_TARGET_TOKEN) forwarded to the publication target only when the client sends no token of its own. The default model is passthrough -- the publisher's own token -- so this is Nothing on the common path.

  • pubInboundToken :: Maybe Secret

    The optional inbound edge token a client must present (ECLUSE_AUTH_TOKEN), the same gate the read paths apply; Nothing leaves the edge open.

  • pubLimits :: Limits

    The response-bound budget enforced on the publication target's response, carried for symmetry with the read paths.

  • pubHelp :: Maybe HelpMessage

    The operator help message appended to a publish denial, if configured.

  • pubRelayPublish :: Limits -> Manager -> Text -> Maybe Secret -> PackageName -> ByteString -> IO (Either UrlFormationError PublishRelayResponse)

    Relay a publish document to the publication target, returning its response.

  • pubCanonicaliseName :: Text -> Maybe PackageName

    Canonicalise a raw package-name string to a PackageName, or Nothing if it cannot be parsed. Used by the body-name agreement guard.

Mount binding

data MountBinding Source #

A mount: a path prefix bound to a registry, carrying that registry's complete ecosystem wiring. Dispatch matches a request's leading path segments to bindingPrefix, strips them, and routes the remainder through the rest of the binding.

The prefix is a NonEmpty list of segments ("npm" :| [] for a /npm mount): every registry is path-mounted, so a root mount -- which would force a URL change on every consumer the day a second ecosystem is added -- is unrepresentable rather than merely discouraged. Bundling the classifier, serve dependencies, and renderer into one record means a mount cannot be half-wired: there is no default to fall back to.

Constructors

MountBinding 

Fields

Per-request context

data RequestCtx Source #

The context one request is served through: the request runtime ServeRuntime paired with the MountBinding the request matched. A concrete record with plain accessors -- ctxRuntime and ctxMount -- so a handler reads the shared runtime and its per-mount wiring from one place rather than as explicit arguments.

Dispatch builds it once per request; the handler reads it through the Handler reader.

Constructors

RequestCtx 

Fields

  • ctxRuntime :: ServeRuntime

    The request runtime -- the data-plane managers, the caches and queue, the recording ports.

  • ctxMount :: MountBinding

    The mount the request matched, carrying its complete ecosystem wiring.

Instances

Instances details
MonadReader RequestCtx Handler Source # 
Instance details

Defined in Ecluse.Core.Server.Context

The handler monad

data Handler a Source #

The request hot path's monad: a reader over the per-request RequestCtx layered on katip's logging context.

A newtype over ReaderT RequestCtx (KatipContextT IO) so its instances are this module's to control and call sites name one concrete monad. The derived instances give reader access to the context (MonadReader RequestCtx), arbitrary effects (MonadIO), the unlift capability (MonadUnliftIO) the serve path's concurrently/bracket need, and the katip classes (Katip, KatipContext) so a structured log call composes through the ambient context the dispatch boundary establishes.

The katip base is a reader, never a StateT, so logging context behaves correctly across the serve path's concurrent fetches (see docs/architecture/technology-stack.md → "Key Decisions").

Instances

Instances details
MonadIO Handler Source # 
Instance details

Defined in Ecluse.Core.Server.Context

Methods

liftIO :: IO a -> Handler a #

Applicative Handler Source # 
Instance details

Defined in Ecluse.Core.Server.Context

Methods

pure :: a -> Handler a #

(<*>) :: Handler (a -> b) -> Handler a -> Handler b #

liftA2 :: (a -> b -> c) -> Handler a -> Handler b -> Handler c #

(*>) :: Handler a -> Handler b -> Handler b #

(<*) :: Handler a -> Handler b -> Handler a #

Functor Handler Source # 
Instance details

Defined in Ecluse.Core.Server.Context

Methods

fmap :: (a -> b) -> Handler a -> Handler b #

(<$) :: a -> Handler b -> Handler a #

Monad Handler Source # 
Instance details

Defined in Ecluse.Core.Server.Context

Methods

(>>=) :: Handler a -> (a -> Handler b) -> Handler b #

(>>) :: Handler a -> Handler b -> Handler b #

return :: a -> Handler a #

Katip Handler Source # 
Instance details

Defined in Ecluse.Core.Server.Context

KatipContext Handler Source # 
Instance details

Defined in Ecluse.Core.Server.Context

MonadUnliftIO Handler Source # 
Instance details

Defined in Ecluse.Core.Server.Context

Methods

withRunInIO :: ((forall a. Handler a -> IO a) -> IO b) -> Handler b Source #

MonadReader RequestCtx Handler Source # 
Instance details

Defined in Ecluse.Core.Server.Context

runHandler :: LogEnv -> SimpleLogPayload -> RequestCtx -> Handler a -> IO a Source #

Run a Handler against the RequestCtx dispatch built for the request and the katip logging environment and initial context the dispatch boundary supplies, yielding the underlying IO action the server's continuation runs in. This is the boundary where the serve path's Handler code is discharged to IO.

The LogEnv (the structured-log scribes) and the initial context payload are passed in rather than read from the runtime, so the application owns the log stream and the trace-correlation dd enrichment: it resolves the dd object for the request and hands it here as the initial context, so every line a handler emits carries dd for trace-to-log correlation. A handler narrows the namespace or adds package/version/rule context with katip's combinators on top as it logs.