shibuya-kiroku-adapter 0.5.0.2 → 0.5.1.0
raw patch · 4 files changed
+86/−9 lines, 4 filesdep ~shibuya-corePVP ok
version bump matches the API change (PVP)
Dependency ranges changed: shibuya-core
API changes (from Hackage documentation)
Files
- CHANGELOG.md +19/−0
- shibuya-kiroku-adapter.cabal +4/−4
- src/Shibuya/Adapter/Kiroku/Convert.hs +17/−2
- test/Main.hs +46/−3
CHANGELOG.md view
@@ -1,5 +1,24 @@ # Changelog +## 0.5.1.0 — 2026-08-15++### Other Changes++* Requires `shibuya-core >=0.9 && <0.10`, which adds the+ `ApplicationFailure DeadLetterCode Text` dead-letter reason.+* `toKirokuDeadLetterReason` now translates `ApplicationFailure` into Kiroku's+ `DeadLetterOther`. The summary is Shibuya's canonical `code: detail`+ rendering, and the structured `reason` JSONB is+ `{"kind":"other","summary":…,"detail":{"code":…,"detail":…}}` so the+ application-owned code is queryable as `reason->'detail'->>'code'` without+ parsing the summary.+* The `PoisonPill`, `InvalidPayload`, and `MaxRetriesExceeded` translations are+ unchanged, so existing `dead_letters` rows keep their encoding. No+ `shibuya-kiroku-adapter` API changed.+* Build with `-Werror=incomplete-patterns`. This package translates between+ dependency-owned sum types, where an upstream constructor addition otherwise+ degrades a translation into a runtime failure behind a green `-Wall` build.+ ## 0.5.0.2 — 2026-08-13 ### Other Changes
shibuya-kiroku-adapter.cabal view
@@ -1,6 +1,6 @@ cabal-version: 3.0 name: shibuya-kiroku-adapter-version: 0.5.0.2+version: 0.5.1.0 synopsis: Kiroku event store adapter for the Shibuya queue processing framework @@ -36,7 +36,7 @@ OverloadedLabels OverloadedStrings - ghc-options: -Wall+ ghc-options: -Wall -Werror=incomplete-patterns library import: common@@ -51,7 +51,7 @@ , hs-opentelemetry-api ^>=1.0 , hs-opentelemetry-semantic-conventions ^>=1.40 , kiroku-store ^>=0.7- , shibuya-core >=0.8 && <0.9+ , shibuya-core >=0.9 && <0.10 , stm >=2.5 && <2.6 , streamly-core >=0.3 && <0.4 , text >=2.0 && <2.2@@ -81,7 +81,7 @@ , kiroku-store ^>=0.7 , kiroku-test-support , lens >=5.2 && <5.4- , shibuya-core >=0.8 && <0.9+ , shibuya-core >=0.9 && <0.10 , shibuya-kiroku-adapter , stm >=2.5 && <2.6 , streamly-core >=0.3 && <0.4
src/Shibuya/Adapter/Kiroku/Convert.hs view
@@ -34,7 +34,7 @@ import Control.Concurrent.STM (atomically, tryPutTMVar) import Control.Monad (void)-import Data.Aeson (Value (..))+import Data.Aeson (Value (..), object, (.=)) import Data.Aeson.Key qualified as Key import Data.Aeson.KeyMap qualified as KM import Data.HashMap.Strict (HashMap)@@ -152,12 +152,27 @@ AckDeadLetter reason -> DeadLetter (toKirokuDeadLetterReason attempt reason) AckHalt _ -> Continue --- | Translate a Shibuya 'Ack.DeadLetterReason' into a Kiroku 'DeadLetterReason'.+{- | Translate a Shibuya 'Ack.DeadLetterReason' into a Kiroku 'DeadLetterReason'.++The three framework-owned reasons keep their dedicated Kiroku constructors, so+their @reason_summary@ and @reason@ JSONB encodings are unchanged.++'Ack.ApplicationFailure' (added in @shibuya-core@ 0.9) has no Kiroku+counterpart — Kiroku deliberately knows nothing about application-owned+dead-letter codes — so it maps to 'DeadLetterOther'. The summary is Shibuya's+canonical @code: detail@ rendering, and the structured JSON keeps the code in+its own field so operators can query @reason->'detail'->>'code'@ without+parsing the summary text.+-} toKirokuDeadLetterReason :: Word -> Ack.DeadLetterReason -> DeadLetterReason toKirokuDeadLetterReason attempt = \case Ack.PoisonPill detail -> DeadLetterPoison detail Ack.InvalidPayload detail -> DeadLetterInvalid detail Ack.MaxRetriesExceeded -> DeadLetterMaxAttempts (fromIntegral attempt)+ reason@(Ack.ApplicationFailure code detail) ->+ DeadLetterOther+ (Ack.renderDeadLetterReason reason)+ (object ["code" .= Ack.deadLetterCodeText code, "detail" .= detail]) {- | Convert a 'RecordedEvent' to a Shibuya 'Envelope'.
test/Main.hs view
@@ -27,6 +27,7 @@ import Hasql.Session qualified as Session import Kiroku.Store import Kiroku.Store.SQL qualified as SQL+import Kiroku.Store.Subscription.Types qualified as KTypes import Kiroku.Store.Subscription.Worker (withFetchBatchHookForTest) import Kiroku.Test.Postgres (withMigratedTestDatabase, withSharedMigratedPostgres) import OpenTelemetry.Attributes (toAttribute)@@ -40,7 +41,12 @@ kirokuConsumerGroupProcessors, kirokuConsumerGroupProcessorsWith, )-import Shibuya.Adapter.Kiroku.Convert (KirokuEnvelopeAttrs, kirokuEnvelopeAttrs, toEnvelope)+import Shibuya.Adapter.Kiroku.Convert (+ KirokuEnvelopeAttrs,+ kirokuEnvelopeAttrs,+ toEnvelope,+ toKirokuDeadLetterReason,+ ) import Shibuya.App ( ProcessorId (..), QueueProcessor (..),@@ -137,6 +143,35 @@ HashMap.lookup "kiroku.consumer_group.member" attributes `shouldBe` Just (toAttribute (2 :: Int64)) + describe "toKirokuDeadLetterReason" $ do+ it "keeps the framework reasons on their dedicated Kiroku constructors" $ do+ toKirokuDeadLetterReason 3 (PoisonPill "bad event")+ `shouldBe` KTypes.DeadLetterPoison "bad event"+ toKirokuDeadLetterReason 3 (InvalidPayload "not json")+ `shouldBe` KTypes.DeadLetterInvalid "not json"+ toKirokuDeadLetterReason 3 MaxRetriesExceeded+ `shouldBe` KTypes.DeadLetterMaxAttempts 3++ it "maps an application failure to DeadLetterOther with a queryable code (shibuya-core 0.9)" $ do+ code <-+ either (E.throwIO . userError . T.unpack) pure $+ Ack.mkDeadLetterCode "billing.card_declined"+ let reason =+ toKirokuDeadLetterReason 1 (Ack.ApplicationFailure code "card declined by issuer")++ KTypes.deadLetterSummary reason+ `shouldBe` "billing.card_declined: card declined by issuer"+ KTypes.deadLetterReasonJson reason+ `shouldBe` Aeson.object+ [ "kind" Aeson..= ("other" :: Text)+ , "summary" Aeson..= ("billing.card_declined: card declined by issuer" :: Text)+ , "detail"+ Aeson..= Aeson.object+ [ "code" Aeson..= ("billing.card_declined" :: Text)+ , "detail" Aeson..= ("card declined by issuer" :: Text)+ ]+ ]+ describe "consumer group policy" $ do it "accepts Serial member concurrency as (PartitionedInOrder, Serial)" $ consumerGroupPolicy Serial `shouldBe` Right (PartitionedInOrder, Serial)@@ -982,8 +1017,16 @@ -- The group is presented as N processors, each pinned to the -- group-level PartitionedInOrder contract + per-member Serial. liftIO $ length processors `shouldBe` 4- let policies = map (\(_, QueueProcessor _ _ ord conc) -> (ord, conc)) processors- liftIO $ policies `shouldBe` replicate 4 (PartitionedInOrder, Serial)+ -- 'Nothing' marks a batching processor, which the+ -- consumer group must never produce.+ let policies =+ map+ ( \(_, p) -> case p of+ QueueProcessor{ordering, concurrency} -> Just (ordering, concurrency)+ BatchingProcessor{} -> Nothing+ )+ processors+ liftIO $ policies `shouldBe` replicate 4 (Just (PartitionedInOrder, Serial)) -- The member index is readable off the ProcessorId. let pids = map (\(ProcessorId p, _) -> p) processors liftIO $