git.iko.soy/rowtype-yoga/purescript-yoga-om-layer
on main help sign in
Add scoped layers with acquire/release semantics
commit6d86a8fbe9815fdb36605ff5acf52bedd44648cc authorMark <mark.eibes@gmail.com> dateFri 13 Feb 2026, 20:30 (8 months ago) parent3e2f690
2 files changed+38−23
src/Yoga/Om/Layer.purs+28−13 test/Test/Main.purs+10−10
src/Yoga/Om/Layer.purs+28−13
@@ -8,6 +8,7 @@ module Yoga.Om.Layer
88 , bracketLayer
99 , runLayer
1010 , runScoped
11+ , withScoped
1112 , combineRequirements
1213 , provide
1314 , (>->)
@@ -78,7 +79,9 @@ makeScopedLayer
7879 -> OmLayer req prov err
7980makeScopedLayer acquire release = OmLayer do
8081 prov <- acquire
81− pure (prov /\ Finalizers [ release prov ])
82+ pure (prov /\ finalizer prov)
83+ where
84+ finalizer prov = Finalizers [ release prov ]
8285
8386-- | Bracket-style scoped layer: acquire a resource, build provisions from it,
8487-- | and register a finalizer for the resource.
@@ -91,7 +94,9 @@ bracketLayer
9194bracketLayer acquire release use = OmLayer do
9295 resource <- acquire
9396 prov <- use resource
94− pure (prov /\ Finalizers [ release resource ])
97+ pure (prov /\ finalizer resource)
98+ where
99+ finalizer resource = Finalizers [ release resource ]
95100
96101-- =============================================================================
97102-- Custom Type Errors for Missing Dependencies
@@ -180,17 +185,26 @@ runLayer _ctx layer = unsafeCastLayer layer <#> \(prov /\ _) -> prov
180185 unsafeCastLayer :: OmLayer req prov err -> Om (Record available) err (Record prov /\ Finalizers)
181186 unsafeCastLayer = unsafeCoerce
182187
183−-- | Run a fully-provided layer within a scope, passing the provisions to a
184−-- | callback. All finalizers are guaranteed to run in reverse order when the
185−-- | callback completes, whether by success, failure, or interruption.
188+-- | Build a fully-provided layer, run finalizers, return the provisions.
189+-- | Useful when you just need the built services.
186190runScoped
191+ :: forall prov
192+ . OmLayer () prov ()
193+ -> Aff (Record prov)
194+runScoped layer = withScoped layer pure
195+
196+-- | Build a fully-provided layer and pass the provisions to a callback.
197+-- | All finalizers run in reverse order when the callback completes,
198+-- | whether by success, failure, or interruption.
199+withScoped
187200 :: forall prov a
188201 . OmLayer () prov ()
189202 -> (Record prov -> Aff a)
190203 -> Aff a
191−runScoped (OmLayer om) callback = generalBracket acquire conditions use
204+withScoped (OmLayer om) callback = generalBracket acquire conditions use
192205 where
193− acquire = Om.runOm {} { exception: \_ -> pure (unsafeCoerce {} /\ mempty) } om
206+ acquire = Om.runOm {} errorHandlers om
207+ errorHandlers = { exception: \_ -> pure (unsafeCoerce {} /\ mempty) }
194208 conditions =
195209 { completed: \_ (_ /\ fins) -> runFinalizers fins
196210 , failed: \_ (_ /\ fins) -> runFinalizers fins
@@ -222,9 +236,9 @@ combineRequirements
222236 -> OmLayer req2 prov2 err2
223237 -> OmLayer reqDeduped provMerged ()
224238combineRequirements (OmLayer build1) (OmLayer build2) = OmLayer do
225− rec1 /\ fins1 <- Om.expand build1
226− rec2 /\ fins2 <- Om.expand build2
227− pure (Record.merge rec1 rec2 /\ (fins1 <> fins2))
239+ prov1 /\ fins1 <- Om.expand build1
240+ prov2 /\ fins2 <- Om.expand build2
241+ pure (Record.merge prov1 prov2 /\ (fins1 <> fins2))
228242
229243-- =============================================================================
230244-- Vertical composition — feed output of one layer into input of another
@@ -243,9 +257,10 @@ provide
243257 -> OmLayer req prov2 ()
244258provide (OmLayer layer2) (OmLayer layer1) = OmLayer do
245259 prov1 /\ fins1 <- Om.expand layer1
246− prov2 /\ fins2 <- liftAff $ Om.runOm prov1
247− { exception: \_ -> pure (unsafeCoerce {} /\ mempty) }
248− (Om.expand layer2)
260+ prov2 /\ fins2 <- runLayer2 prov1
249261 pure (prov2 /\ (fins1 <> fins2))
262+ where
263+ runLayer2 ctx = liftAff $ Om.runOm ctx errorHandlers (Om.expand layer2)
264+ errorHandlers = { exception: \_ -> pure (unsafeCoerce {} /\ mempty) }
250265
251266infixl 9 provide as >->
test/Test/Main.purs+10−10
@@ -16,7 +16,7 @@ import Test.Spec.Assertions (shouldEqual)
1616import Test.Spec.Reporter (consoleReporter)
1717import Test.Spec.Runner (runSpec)
1818import Yoga.Om as Om
19−import Yoga.Om.Layer (OmLayer(..), Finalizers, makeLayer, makeScopedLayer, bracketLayer, combineRequirements, runScoped, provide)
19+import Yoga.Om.Layer (OmLayer(..), Finalizers, makeLayer, makeScopedLayer, bracketLayer, combineRequirements, runScoped, withScoped, provide)
2020
2121-- Example types
2222type Config = { port :: Int, host :: String }
@@ -151,20 +151,20 @@ main = launchAff_ $ runSpec [ consoleReporter ] do
151151
152152 combined = combineRequirements layerA layerBC
153153
154− liftAff $ runScoped combined \_ -> pure unit
154+ _ <- liftAff $ runScoped combined
155155 finalLog <- liftEffect $ Ref.read log
156156 finalLog `shouldEqual` [ "release-C", "release-B", "release-A" ]
157157
158158 it "runs finalizers when the callback throws" do
159159 log <- liftEffect $ Ref.new []
160160 let
161+ layer :: OmLayer () (value :: String) ()
161162 layer = makeScopedLayer
162163 (pure { value: "acquired" })
163164 (\_ -> liftEffect $ Ref.modify_ (_ <> [ "released" ]) log)
164165
165− -- The callback throws, but finalizers should still run
166− _ <- liftAff $ runScoped layer (\_ -> throwError (error "callback failed"))
167− # try
166+ _ <- liftAff $ try $ withScoped layer \_ ->
167+ throwError (error "callback failed")
168168 finalLog <- liftEffect $ Ref.read log
169169 finalLog `shouldEqual` [ "released" ]
170170
@@ -186,8 +186,8 @@ main = launchAff_ $ runSpec [ consoleReporter ] do
186186
187187 composed = upperLayer `provide` baseLayer
188188
189− result <- liftAff $ runScoped composed \r -> pure r.upper
190− result `shouldEqual` "base-value-extended"
189+ result <- liftAff $ runScoped composed
190+ result.upper `shouldEqual` "base-value-extended"
191191 finalLog <- liftEffect $ Ref.read log
192192 finalLog `shouldEqual` [ "release-upper", "release-base" ]
193193
@@ -202,8 +202,8 @@ main = launchAff_ $ runSpec [ consoleReporter ] do
202202 (\_ -> liftEffect $ Ref.modify_ (_ <> [ "release" ]) log)
203203 (\n -> pure { value: n })
204204
205− result <- liftAff $ runScoped layer \r -> pure r.value
206− result `shouldEqual` 42
205+ result <- liftAff $ runScoped layer
206+ result.value `shouldEqual` 42
207207 finalLog <- liftEffect $ Ref.read log
208208 finalLog `shouldEqual` [ "acquire", "release" ]
209209
@@ -220,7 +220,7 @@ main = launchAff_ $ runSpec [ consoleReporter ] do
220220
221221 combined = combineRequirements scopedLayer plainLayer
222222
223− result <- liftAff $ runScoped combined \r -> pure r
223+ result <- liftAff $ runScoped combined
224224 result.scoped `shouldEqual` "yes"
225225 result.plain `shouldEqual` "no-finalizer"
226226 finalLog <- liftEffect $ Ref.read log