packages feed

effectful-data-cache 0.1.0.2 → 0.2.0.1

raw patch · 6 files changed

+93/−181 lines, 6 filesdep −hs-opentelemetry-apidep −hs-opentelemetry-exporter-in-memorydep −hs-opentelemetry-sdkPVP ok

version bump matches the API change (PVP)

Dependencies removed: hs-opentelemetry-api, hs-opentelemetry-exporter-in-memory, hs-opentelemetry-sdk, text, unordered-containers

API changes (from Hackage documentation)

- Effectful.Cache.OpenTelemetry: traceCache :: forall k v (es :: [Effect]) a. (Cache k v :> es, IOE :> es) => Tracer -> Eff es a -> Eff es a

Files

+ CHANGELOG.md view
@@ -0,0 +1,29 @@+## [2.0.1](https://github.com/haskell-github-trust/effectful-data-cache/compare/2.0.0...2.0.1) (2026-10-25)++## [2.0.0](https://github.com/haskell-github-trust/effectful-data-cache/compare/1.0.4...2.0.0) (2026-10-09)++### ⚠ BREAKING CHANGES++* split into base & otel packages++### Code Refactoring++* split into base & otel packages ([782b664](https://github.com/haskell-github-trust/effectful-data-cache/commit/782b664762aa56f2c9515ec3cff7cc4d612dd630))++## [1.0.4](https://github.com/haskell-github-trust/effectful-data-cache/compare/1.0.3...1.0.4) (2026-10-02)++## [1.0.3](https://github.com/haskell-github-trust/effectful-data-cache/compare/1.0.2...1.0.3) (2026-10-01)++## [1.0.2](https://github.com/haskell-github-trust/effectful-data-cache/compare/1.0.1...1.0.2) (2026-09-29)++## [1.0.1](https://github.com/haskell-github-trust/effectful-data-cache/compare/1.0.0...1.0.1) (2026-09-28)++### Features++* add otel telemetry ([9c0cdf2](https://github.com/haskell-github-trust/effectful-data-cache/commit/9c0cdf2a8e001644bde24cdcd0524243ee1651f7))++## 1.0.0 (2026-09-28)++### Features++* initial commit ([fefe10b](https://github.com/haskell-github-trust/effectful-data-cache/commit/fefe10b9003d85f70cb0348c7f715f3a1125bd4e))
README.md view
@@ -17,16 +17,5 @@  ## Tracing -`traceCache` wraps every operation in an OpenTelemetry span-(`cache.insert`, `cache.lookup`, …), with `cache.hit` on lookups and-`cache.size` on `size`/`keys`. Keys and values are never recorded.--```haskell-import Effectful.Cache.OpenTelemetry (traceCache)--main = runEff . runCache Nothing . traceCache tracer $ do-  insert "key" (42 :: Int)-```--It is an interposer, so it layers over any interpreter and can be omitted-without touching call sites.+See [effectful-data-cache-opentelemetry](effectful-data-cache-opentelemetry)+for OpenTelemetry instrumentation.
effectful-data-cache.cabal view
@@ -5,7 +5,7 @@ -- see: https://github.com/sol/hpack  name:           effectful-data-cache-version:        0.1.0.2+version:        0.2.0.1 synopsis:       Data cache effect for the `effectful` system description:    Please see the README on GitHub at <https://github.com/haskell-github-trust/effectful-data-cache#readme> category:       cache@@ -19,6 +19,8 @@ build-type:     Simple extra-source-files:     README.md+extra-doc-files:+    CHANGELOG.md  source-repository head   type: git@@ -27,7 +29,6 @@ library   exposed-modules:       Effectful.Cache-      Effectful.Cache.OpenTelemetry   other-modules:       Paths_effectful_data_cache   autogen-modules:@@ -41,15 +42,12 @@     , clock ==0.8.*     , effectful-core >=2.3 && <3     , hashable >=1.5 && <2-    , hs-opentelemetry-api ==0.3.*-    , text >=2.1 && <3   default-language: Haskell2010  test-suite effectful-data-cache-test   type: exitcode-stdio-1.0   main-is: Spec.hs   other-modules:-      Effectful.Cache.OpenTelemetrySpec       Effectful.CacheSpec       Paths_effectful_data_cache   autogen-modules:@@ -66,10 +64,5 @@     , effectful-core     , effectful-data-cache     , hashable >=1.5 && <2-    , hs-opentelemetry-api-    , hs-opentelemetry-exporter-in-memory-    , hs-opentelemetry-sdk     , hspec-    , text-    , unordered-containers   default-language: Haskell2010
src/Effectful/Cache.hs view
@@ -7,6 +7,12 @@ {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE TypeOperators #-} +-- | Dynamic effect over "Data.Cache", an in-memory key/value store with+-- optional per-item expiration.+--+-- > runEff . runCache @Text @Int Nothing $ do+-- >   insert "a" 1+-- >   lookup @Text @Int "a" module Effectful.Cache (   Cache (..),   insert,@@ -41,49 +47,63 @@ import System.Clock (TimeSpec) import Prelude hiding (lookup) --- | @since 0.1.0.0+-- | Cache of values @v@ indexed by keys @k@.+--+-- @since 0.1.0.0 data Cache k v :: Effect where+  -- | See 'insert'.   Insert     :: Hashable k     => k     -> v     -> Cache k v m ()+  -- | See 'insert''.   Insert'     :: Hashable k     => Maybe TimeSpec     -> k     -> v     -> Cache k v m ()+  -- | See 'lookup'.   Lookup     :: Hashable k     => k     -> Cache k v m (Maybe v)+  -- | See 'lookup''.   Lookup'     :: Hashable k     => k     -> Cache k v m (Maybe v)+  -- | See 'keys'.   Keys     :: Hashable k => Cache k v m [k]+  -- | See 'delete'.   Delete     :: Hashable k     => k     -> Cache k v m ()+  -- | See 'filterWithKey'.   FilterWithKey     :: Hashable k     => (k -> v -> Bool)     -> Cache k v m ()+  -- | See 'purge'.   Purge     :: Hashable k     => Cache k v m ()+  -- | See 'purgeExpired'.   PurgeExpired     :: Hashable k     => Cache k v m ()+  -- | See 'size'.   Size     :: Hashable k     => Cache k v m Int+  -- | See 'defaultExpiration'.   DefaultExpiration     :: Hashable k     => Cache k v m (Maybe TimeSpec)+  -- | See 'setDefaultExpiration'.   SetDefaultExpiration     :: Hashable k     => Maybe TimeSpec@@ -91,7 +111,9 @@  type instance DispatchOf (Cache k v) = Dynamic --- | @since 0.1.0.0+-- | Insert an item with the cache's default expiration.+--+-- @since 0.1.0.0 insert   :: forall k v es    . Cache k v :> es@@ -101,7 +123,10 @@   -> Eff es () insert k v = send (Insert k v :: Cache k v (Eff es) ()) --- | @since 0.1.0.0+-- | Insert an item with an explicit expiration, relative to now;+-- 'Nothing' never expires. The default expiration is ignored.+--+-- @since 0.1.0.0 insert'   :: forall k v es    . Cache k v :> es@@ -112,7 +137,9 @@   -> Eff es () insert' ts k v = send (Insert' ts k v :: Cache k v (Eff es) ()) --- | @since 0.1.0.0+-- | Look up a non-expired item, evicting it if expired.+--+-- @since 0.1.0.0 lookup   :: forall k v es    . Cache k v :> es@@ -122,6 +149,7 @@ lookup k = send (Lookup k :: Cache k v (Eff es) (Maybe v))  -- | Like 'lookup' but never evicts the expired entry it read.+-- -- @since 0.1.0.0 lookup'   :: forall k v es@@ -131,7 +159,9 @@   -> Eff es (Maybe v) lookup' k = send (Lookup' k :: Cache k v (Eff es) (Maybe v)) --- | @since 0.1.0.0+-- | All keys in the store, including expired ones.+--+-- @since 0.1.0.0 keys   :: forall k v es    . Cache k v :> es@@ -139,7 +169,9 @@   => Eff es [k] keys = send (Keys :: Cache k v (Eff es) [k]) --- | @since 0.1.0.0+-- | Delete an item; no-op if absent.+--+-- @since 0.1.0.0 delete   :: forall k v es    . Cache k v :> es@@ -148,7 +180,10 @@   -> Eff es () delete k = send (Delete k :: Cache k v (Eff es) ()) --- | @since 0.1.0.0+-- | Keep only items satisfying the predicate, which may be called+-- on expired items.+--+-- @since 0.1.0.0 filterWithKey   :: forall k v es    . Cache k v :> es@@ -157,7 +192,9 @@   -> Eff es () filterWithKey p = send (FilterWithKey p :: Cache k v (Eff es) ()) --- | @since 0.1.0.0+-- | Delete all items.+--+-- @since 0.1.0.0 purge   :: forall k v es    . Cache k v :> es@@ -165,7 +202,9 @@   => Eff es () purge = send (Purge :: Cache k v (Eff es) ()) --- | @since 0.1.0.0+-- | Delete all expired items, atomically.+--+-- @since 0.1.0.0 purgeExpired   :: forall k v es    . Cache k v :> es@@ -173,7 +212,9 @@   => Eff es () purgeExpired = send (PurgeExpired :: Cache k v (Eff es) ()) --- | @since 0.1.0.0+-- | Number of items, including expired ones.+--+-- @since 0.1.0.0 size   :: forall k v es    . Cache k v :> es@@ -181,7 +222,9 @@   => Eff es Int size = send (Size :: Cache k v (Eff es) Int) --- | @since 0.1.0.0+-- | Expiration applied by 'insert'.+--+-- @since 0.1.0.0 defaultExpiration   :: forall k v es    . Cache k v :> es@@ -189,7 +232,9 @@   => Eff es (Maybe TimeSpec) defaultExpiration = send (DefaultExpiration :: Cache k v (Eff es) (Maybe TimeSpec)) --- | @since 0.1.0.0+-- | Change the expiration applied by subsequent 'insert's.+--+-- @since 0.1.0.0 setDefaultExpiration   :: forall k v es    . Cache k v :> es@@ -199,6 +244,7 @@ setDefaultExpiration ts = send (SetDefaultExpiration ts :: Cache k v (Eff es) ())  -- | Run against a fresh store with the given default expiration.+-- -- @since 0.1.0.0 runCache   :: forall k v es a@@ -211,6 +257,7 @@   runCacheWith c eff  -- | Run against an existing 'C.Cache', e.g. one shared with non-effectful code.+-- -- @since 0.1.0.0 runCacheWith   :: forall k v es a
− src/Effectful/Cache/OpenTelemetry.hs
@@ -1,67 +0,0 @@-{-# LANGUAGE AllowAmbiguousTypes #-}-{-# LANGUAGE DataKinds #-}-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE GADTs #-}-{-# LANGUAGE LambdaCase #-}-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE ScopedTypeVariables #-}-{-# LANGUAGE TypeApplications #-}-{-# LANGUAGE TypeOperators #-}---- | OpenTelemetry instrumentation for 'Cache', layered over any interpreter.------ > runEff . runCache Nothing . traceCache tracer $ do ...-module Effectful.Cache.OpenTelemetry (-  traceCache,-) where--import Data.Maybe (isJust)-import Data.Text (Text)-import Effectful-import Effectful.Cache (Cache (..))-import Effectful.Dispatch.Dynamic (interpose, passthrough)-import OpenTelemetry.Trace.Core (-  Tracer,-  addAttribute,-  defaultSpanArguments,-  inSpan',- )---- | Wrap every cache operation in a span. Keys and values are never recorded:--- unbounded cardinality, and likely user data.------ @since 0.1.0.1-traceCache-  :: forall k v es a-   . Cache k v :> es-  => IOE :> es-  => Tracer-  -> Eff es a-  -> Eff es a-traceCache tracer = interpose @(Cache k v) $ \env op ->-  inSpan' tracer (opName op) defaultSpanArguments $ \sp -> do-    r <- passthrough env op-    case op of-      Lookup _ -> addAttribute sp "cache.hit" (isJust r)-      Lookup' _ -> addAttribute sp "cache.hit" (isJust r)-      Size -> addAttribute sp "cache.size" r-      Keys -> addAttribute sp "cache.size" (length r)-      _ -> pure ()-    pure r--opName-  :: Cache k v m a-  -> Text-opName = \case-  Insert{} -> "cache.insert"-  Insert'{} -> "cache.insert"-  Lookup{} -> "cache.lookup"-  Lookup'{} -> "cache.lookup"-  Keys{} -> "cache.keys"-  Delete{} -> "cache.delete"-  FilterWithKey{} -> "cache.filterWithKey"-  Purge{} -> "cache.purge"-  PurgeExpired{} -> "cache.purgeExpired"-  Size{} -> "cache.size"-  DefaultExpiration{} -> "cache.defaultExpiration"-  SetDefaultExpiration{} -> "cache.setDefaultExpiration"
− test/Effectful/Cache/OpenTelemetrySpec.hs
@@ -1,79 +0,0 @@-{-# LANGUAGE DataKinds #-}-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE TypeApplications #-}--module Effectful.Cache.OpenTelemetrySpec (spec) where--import Data.IORef (readIORef)-import Data.Text (Text)-import Effectful-import Effectful.Cache-import Effectful.Cache.OpenTelemetry (traceCache)-import OpenTelemetry.Attributes (Attribute (..), PrimitiveAttribute (..), lookupAttribute)-import OpenTelemetry.Exporter.InMemory.Span (inMemoryListExporter)-import OpenTelemetry.Trace (-  ImmutableSpan (..),-  createTracerProvider,-  emptyTracerProviderOptions,-  makeTracer,-  shutdownTracerProvider,-  tracerOptions,- )-import Test.Hspec-import Prelude hiding (lookup)---- | Run the given cache program under a traced interpreter, returning its--- result plus every span that was exported.-traced-  :: Eff '[Cache String Int, IOE] a-  -> IO (a, [ImmutableSpan])-traced prog = do-  (processor, ref) <- inMemoryListExporter-  provider <- createTracerProvider [processor] emptyTracerProviderOptions-  let tracer = makeTracer provider "effectful-data-cache-test" tracerOptions-  r <- runEff . runCache @String @Int Nothing $ traceCache @String @Int tracer prog-  shutdownTracerProvider provider-  spans <- readIORef ref-  pure (r, reverse spans)--attr-  :: ImmutableSpan-  -> Text-  -> Maybe Attribute-attr s k = lookupAttribute (spanAttributes s) k--spec :: Spec-spec = do-  it "emits a span per operation" $ do-    (_, spans) <- traced $ do-      insert @String @Int "a" (1 :: Int)-      _ <- lookup @String @Int "a"-      purge @String @Int-    map spanName spans `shouldBe` ["cache.insert", "cache.lookup", "cache.purge"]--  it "records hits and misses" $ do-    (_, spans) <- traced $ do-      insert @String @Int "a" (1 :: Int)-      _ <- lookup @String @Int "a"-      _ <- lookup @String @Int "nope"-      pure ()-    let hits = [attr s "cache.hit" | s <- spans, spanName s == "cache.lookup"]-    hits-      `shouldBe` [ Just (AttributeValue (BoolAttribute True))-                 , Just (AttributeValue (BoolAttribute False))-                 ]--  it "records the size" $ do-    (_, spans) <- traced $ do-      insert @String @Int "a" (1 :: Int)-      insert @String @Int "b" (2 :: Int)-      _ <- size @String @Int-      pure ()-    [attr s "cache.size" | s <- spans, spanName s == "cache.size"]-      `shouldBe` [Just (AttributeValue (IntAttribute 2))]--  it "still returns the underlying result" $ do-    (r, _) <- traced $ do-      insert @String @Int "a" (1 :: Int)-      lookup @String @Int "a"-    r `shouldBe` Just 1