-- SPDX-FileCopyrightText: 2026 Alexandra de Wit -- -- SPDX-License-Identifier: MIT {- | The cached and served raw document as an __opaque carrier with a private constructor__. The serve path caches an upstream packument's raw document and rebuilds the served body from it, but the neutral pipeline and the metadata cache must /hold/ that document without /reading/ it: every structural operation over it is an injected adapter capability (the fetch that produces it, the assembly that merges it, the serialisation that encodes it). 'CachedDoc' makes that opacity a property the compiler enforces rather than a discipline the neutral code must keep: the constructor is __not exported__, so a module outside this one can cache, thread, and hand back a 'CachedDoc' but cannot inspect what it carries. An ecosystem works in its own representation and crosses this boundary through an inject/project pair ('npmCached' for npm, whose representation is a JSON 'Data.Aeson.Value'): it injects on the way into the cache and pipeline and projects on the way out, at its own capabilities. The cache weighs a held document through 'weighCachedDoc' without projecting it. -} module Ecluse.Core.Registry.CachedDocument ( CachedDoc, weighCachedDoc, npmCached, ) where import Data.Aeson (Value, encode) import Data.ByteString.Lazy qualified as BSL {- | A raw document the metadata cache holds and the neutral serve pipeline threads. The representation: an npm packument as a JSON 'Value'. Exported __abstract__ -- the constructor is private, so only this module's boundary pairs cross it and the neutral core cannot read the document a 'CachedDoc' carries. The derived 'Show' and 'Eq' are a debug and test affordance (a failure message, a round-trip assertion), not a projection: they give the neutral core no way to recover the underlying document to assemble or serialise it. -} newtype CachedDoc = CachedNpm Value deriving stock (CachedDoc -> CachedDoc -> Bool (CachedDoc -> CachedDoc -> Bool) -> (CachedDoc -> CachedDoc -> Bool) -> Eq CachedDoc forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a $c== :: CachedDoc -> CachedDoc -> Bool == :: CachedDoc -> CachedDoc -> Bool $c/= :: CachedDoc -> CachedDoc -> Bool /= :: CachedDoc -> CachedDoc -> Bool Eq, Int -> CachedDoc -> ShowS [CachedDoc] -> ShowS CachedDoc -> String (Int -> CachedDoc -> ShowS) -> (CachedDoc -> String) -> ([CachedDoc] -> ShowS) -> Show CachedDoc forall a. (Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a $cshowsPrec :: Int -> CachedDoc -> ShowS showsPrec :: Int -> CachedDoc -> ShowS $cshow :: CachedDoc -> String show :: CachedDoc -> String $cshowList :: [CachedDoc] -> ShowS showList :: [CachedDoc] -> ShowS Show) {- | A held document's resident-size estimate: the byte length of its compact encoding, the figure the metadata cache weighs an entry by. Computed here, where the arm is known, so the cache need not project the document back to an ecosystem representation to size it. -} weighCachedDoc :: CachedDoc -> Int64 weighCachedDoc :: CachedDoc -> Int64 weighCachedDoc (CachedNpm Value v) = ByteString -> Int64 BSL.length (Value -> ByteString forall a. ToJSON a => a -> ByteString encode Value v) {- | npm's boundary pair: inject a packument 'Value' into a 'CachedDoc', and project one back to the 'Value' npm assembles and serialises over. npm is the injector for every document it later projects, so a document it injected always projects back ('Just'); the 'Maybe' is the projection's type, matched at npm's own capabilities. -} npmCached :: (Value -> CachedDoc, CachedDoc -> Maybe Value) npmCached :: (Value -> CachedDoc, CachedDoc -> Maybe Value) npmCached = (Value -> CachedDoc CachedNpm, \case CachedNpm Value v -> Value -> Maybe Value forall a. a -> Maybe a Just Value v)