diff --git a/Control/Pipe/Zlib.hs b/Control/Pipe/Zlib.hs
deleted file mode 100644
--- a/Control/Pipe/Zlib.hs
+++ /dev/null
@@ -1,59 +0,0 @@
-module Control.Pipe.Zlib (
-  gzip,
-  gunzip,
-  decompress,
-  compress
-  ) where
-
-import Codec.Zlib
-import Control.Monad
-import Control.Monad.IO.Class
-import Control.Pipe
-import Control.Pipe.Combinators
-import qualified Data.ByteString as B
-import Prelude hiding (catch)
-
--- | Gzip compression with default parameters.
-gzip :: MonadIO m => Pipe B.ByteString B.ByteString m r
-gzip = compress 1 (WindowBits 31)
-
--- | Gzip decompression with default parameters.
-gunzip :: MonadIO m => Pipe B.ByteString B.ByteString m r
-gunzip = decompress (WindowBits 31)
-
-decompress
-    :: MonadIO m
-    => WindowBits
-    -> Pipe B.ByteString B.ByteString m r
-decompress config = do
-    inf <- liftIO $ initInflate config
-    let finalize = do chunk <- liftIO $ finishInflate inf
-                      unless (B.null chunk) $ yield chunk
-    forP' finalize $ \x -> do
-      popper <- liftIO $ feedInflate inf x
-      yieldPopper popper
-
-compress
-    :: MonadIO m
-    => Int
-    -> WindowBits
-    -> Pipe B.ByteString B.ByteString m r
-compress level config = do
-    def <- liftIO $ initDeflate level config
-    let finalize = yieldPopper (finishDeflate def)
-    forP' finalize $ \x -> do
-      popper <- liftIO $ feedDeflate def x
-      yieldPopper popper
-
-forP' :: Monad m
-      => Pipe a b m r2
-      -> (a -> Pipe a b m r1)
-      -> Pipe a b m r
-forP' p f = forP f >> p >> discard
-
-yieldPopper :: MonadIO m => Popper -> Pipe a B.ByteString m ()
-yieldPopper pop = do
-  x <- liftIO pop
-  case x of
-    Nothing -> return ()
-    Just chunk -> yield chunk >> yieldPopper pop
diff --git a/LICENSE b/LICENSE
--- a/LICENSE
+++ b/LICENSE
@@ -1,4 +1,5 @@
-Copyright (c) 2012 Paolo Capriotti <p.capriotti@gmail.com>,
+Copyright (c) 2012 Paolo Capriotti <p.capriotti@gmail.com>
+Copyright (c) 2013-2018 Renzo Carbonara <renλren.zone>
 
 All rights reserved.
 
diff --git a/PEOPLE b/PEOPLE
new file mode 100644
--- /dev/null
+++ b/PEOPLE
@@ -0,0 +1,8 @@
+The following people have participated in creating this library, either
+by directly contributing code or by providing thoughtful input in
+discussions about the library design.
+
+Renzo Carbonara
+Paolo Capriotti
+Oliver Charles
+Gabriel Gonzalez
diff --git a/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -0,0 +1,12 @@
+# pipes-zlib
+
+Utilities to deal with zlib compressed streams the **pipes** and libraries.
+
+Check the source or rendered Haddocks for extensive documentation.
+
+This code is licensed under the terms of the so called **3-clause BSD
+license**. Read the file named ``LICENSE`` found in this same directory
+for details.
+
+See the ``PEOPLE`` file to learn about the people involved in this
+effort.
diff --git a/Setup.hs b/Setup.hs
--- a/Setup.hs
+++ b/Setup.hs
@@ -1,2 +1,4 @@
+#! /usr/bin/env nix-shell
+#! nix-shell ./shell.nix -i runghc
 import Distribution.Simple
 main = defaultMain
diff --git a/changelog.md b/changelog.md
new file mode 100644
--- /dev/null
+++ b/changelog.md
@@ -0,0 +1,77 @@
+# Version 0.4.4.2
+
+* Remove upper bound from all dependencies except `base`.
+
+
+# Version 0.4.4.1
+
+* Bump upper bound dependency on `pipes`.
+
+
+# Version 0.4.4
+
+* Depend on `streaming-commons` instead of `zlib` and `zlib-bindings`,
+  as the latter are deprecated.
+
+* Add `Pipes.Zlib.decompress'` and `Pipes.GZip.decompress'`.
+
+* Bump upper bound dependency on `transformers`.
+
+* Add tests.
+
+
+# Version 0.4.3
+
+* Fix usage of the `Producer'` type synonym (#14).
+
+
+# Version 0.4.2.1
+
+* Fix “Codec.Compression.Zlib: premature end of compressed stream” (#13)
+
+
+# Version 0.4.2
+
+* Added the `Pipes/GZip.hs` file that was missing in 0.4.1.
+
+
+# Version 0.4.1
+
+* Added `Pipes.GZip` module.
+
+
+# Version 0.4.0.1
+
+* Bump upper bound dependency on `transformers`.
+
+
+# Version 0.4.0
+
+* Backwards incompatible API. `compress` and `decompress` are now
+  functions of `Producer'`s as they need to perform actions at the
+  beginning and end of input. (Issue #3)
+
+
+# Version 0.3.1
+
+* Dependency upper bounds.
+
+
+# Version 0.3.0
+
+* Upgraded to work with pipes-4.0.0, creating a new backwards
+  incompatible API.
+
+* Generalize base `IO` monad to `MonadIO`.
+
+
+# Version 0.2.0.0
+
+* New backwards incompatible API.
+
+* Based on pipes-3.3 and zlib-bindings.
+
+
+# Up to version 0.1.0
+
+* Based on pipes-core and zlib-bindings.
diff --git a/pipes-zlib.cabal b/pipes-zlib.cabal
--- a/pipes-zlib.cabal
+++ b/pipes-zlib.cabal
@@ -1,27 +1,53 @@
-Name: pipes-zlib
-Version: 0.1.0
-License: BSD3
-License-file: LICENSE
-Author: Paolo Capriotti
-Maintainer: p.capriotti@gmail.com
-Stability: Experimental
-Homepage: https://github.com/pcapriotti/pipes-extra
-Category: Control, Enumerator, Compression
-Build-type: Simple
-Synopsis: Pipes to deal with zipped data.
-Description: Pipes to deal with zipped data.
-Cabal-version: >=1.8
+name:               pipes-zlib
+version:            0.4.4.2
+license:            BSD3
+license-file:       LICENSE
+Copyright:          Copyright (c) Paolo Capriotti 2012, Renzo Carbonara 2013-2018
+author:             Renzo Carbonara, Paolo Capriotti
+maintainer:         renλren.zone
+stability:          Experimental
+homepage:           https://github.com/k0001/pipes-zlib
+bug-reports:        https://github.com/k0001/pipes-zlib/issues
+category:           Pipes, Compression
+build-type:         Simple
+synopsis:           Zlib and GZip compression and decompression for Pipes streams
+description:        Zlib and GZip compression and decompression for Pipes streams
+cabal-version:      >=1.10
+extra-source-files: README.md PEOPLE changelog.md
 
-Source-Repository head
-    Type: git
-    Location: https://github.com/pcapriotti/pipes-extra
+source-repository head
+    type: git
+    location: git://github.com/k0001/pipes-zlib.git
 
-Library
-    Build-Depends:
-        base (== 4.*),
-        transformers (>= 0.2 && < 0.4),
-        pipes-core (== 0.1.*),
-        bytestring (== 0.9.*),
-        zlib-bindings (== 0.1.*)
-    Exposed-Modules:
-        Control.Pipe.Zlib
+library
+    hs-source-dirs:  src
+    exposed-modules: Pipes.Zlib
+                     Pipes.GZip
+    build-depends:   base >= 4.5 && < 5.0
+                   , transformers >= 0.2
+                   , pipes >= 4.0
+                   , bytestring >= 0.9.2.1
+                   , streaming-commons >= 0.1.15
+    ghc-options: -Wall -O2
+    default-language:    Haskell2010
+
+test-suite test
+  type:                exitcode-stdio-1.0
+  hs-source-dirs:      test
+  main-is:             Main.hs
+  build-depends:       base
+                     , pipes-zlib
+                     , pipes
+                     , bytestring
+                     , HUnit
+                     , QuickCheck
+                     , quickcheck-instances
+                     , tasty
+                     , tasty-quickcheck
+                     , tasty-hunit
+  ghc-options:         -threaded -rtsopts -with-rtsopts=-N
+  default-language:    Haskell2010
+
+source-repository head
+  type:     git
+  location: https://github.com/githubuser/twio
diff --git a/src/Pipes/GZip.hs b/src/Pipes/GZip.hs
new file mode 100644
--- /dev/null
+++ b/src/Pipes/GZip.hs
@@ -0,0 +1,71 @@
+{-# LANGUAGE RankNTypes #-}
+
+-- | This module exports utilities to compress and decompress GZip @pipes@
+-- streams.
+
+module Pipes.GZip (
+  -- * Streams
+    decompress
+  , decompress'
+  , compress
+
+  -- * Compression level
+  , Pipes.Zlib.CompressionLevel
+  , Pipes.Zlib.defaultCompression
+  , Pipes.Zlib.noCompression
+  , Pipes.Zlib.bestSpeed
+  , Pipes.Zlib.bestCompression
+  , Pipes.Zlib.compressionLevel
+  ) where
+
+import qualified Data.Streaming.Zlib       as Zlib
+import qualified Data.ByteString           as B
+import           Pipes
+import qualified Pipes.Zlib
+
+--------------------------------------------------------------------------------
+
+-- | Decompress bytes flowing from a 'Producer'.
+--
+-- @
+-- 'decompress' :: 'MonadIO' m
+--            => 'Producer' 'B.ByteString' m r
+--            -> 'Producer' 'B.ByteString' m r
+-- @
+decompress
+  :: MonadIO m
+  => Proxy x' x () B.ByteString m r -- ^ Compressed stream
+  -> Proxy x' x () B.ByteString m r -- ^ Decompressed stream
+decompress = Pipes.Zlib.decompress gzWindowBits
+{-# INLINABLE decompress #-}
+
+-- | Decompress bytes flowing from a 'Producer', returning any leftover input
+-- that follows the compressed stream.
+decompress'
+  :: MonadIO m
+  => Producer B.ByteString m r -- ^ Compressed stream
+  -> Producer B.ByteString m (Either (Producer B.ByteString m r) r)
+     -- ^ Decompressed stream, returning either a 'Producer' of the leftover input
+     -- or the return value from the input 'Producer'.
+decompress' = Pipes.Zlib.decompress' gzWindowBits
+{-# INLINABLE decompress' #-}
+
+
+-- | Compress bytes flowing from a 'Producer'.
+--
+-- @
+-- 'compress' :: 'MonadIO' m
+--          => 'ZC.CompressionLevel'
+--          -> 'Producer' 'B.ByteString' m r
+--          -> 'Producer' 'B.ByteString' m r
+-- @
+compress
+  :: MonadIO m
+  => Pipes.Zlib.CompressionLevel
+  -> Proxy x' x () B.ByteString m r -- ^ Decompressed stream
+  -> Proxy x' x () B.ByteString m r -- ^ Compressed stream
+compress clevel = Pipes.Zlib.compress clevel gzWindowBits
+{-# INLINABLE compress #-}
+
+gzWindowBits :: Zlib.WindowBits
+gzWindowBits = Zlib.WindowBits 31
diff --git a/src/Pipes/Zlib.hs b/src/Pipes/Zlib.hs
new file mode 100644
--- /dev/null
+++ b/src/Pipes/Zlib.hs
@@ -0,0 +1,160 @@
+{-# LANGUAGE RankNTypes #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+
+-- | This module exports utilities to compress and decompress @pipes@ streams
+-- using the zlib compression codec.
+--
+-- If you want to compress or decompress GZip streams, use the "Pipes.GZip"
+-- module instead.
+
+module Pipes.Zlib (
+  -- * Streams
+    decompress
+  , decompress'
+  , compress
+
+  -- * Compression levels
+  , CompressionLevel
+  , defaultCompression
+  , noCompression
+  , bestSpeed
+  , bestCompression
+  , compressionLevel
+
+  -- * Window size
+  -- $ccz-re-export
+  , Z.defaultWindowBits
+  , windowBits
+  ) where
+
+import           Data.Streaming.Zlib       as Z
+import           Control.Exception         (throwIO)
+import           Control.Monad             (unless)
+import qualified Data.ByteString           as B
+import           Pipes
+
+--------------------------------------------------------------------------------
+
+-- | Decompress bytes flowing from a 'Producer'.
+--
+-- See the "Codec.Compression.Zlib" module for details about 'Z.WindowBits'.
+--
+-- @
+-- 'decompress' :: 'MonadIO' m
+--            => 'Z.WindowBits'
+--            => 'Producer' 'B.ByteString' m r
+--            -> 'Producer' 'B.ByteString' m r
+-- @
+decompress
+  :: MonadIO m
+  => Z.WindowBits
+  -> Proxy x' x () B.ByteString m r -- ^ Compressed stream
+  -> Proxy x' x () B.ByteString m r -- ^ Decompressed stream
+decompress wbits p0 = do
+    inf <- liftIO $ Z.initInflate wbits
+    r <- for p0 $ \bs -> do
+       popper <- liftIO (Z.feedInflate inf bs)
+       fromPopper popper
+    bs <- liftIO $ Z.finishInflate inf
+    unless (B.null bs) (yield bs)
+    return r
+{-# INLINABLE decompress #-}
+
+-- | Decompress bytes flowing from a 'Producer', returning any leftover input
+-- that follows the compressed stream.
+decompress'
+  :: MonadIO m
+  => Z.WindowBits
+  -> Producer B.ByteString m r -- ^ Compressed stream
+  -> Producer B.ByteString m (Either (Producer B.ByteString m r) r)
+     -- ^ Decompressed stream, ending with either leftovers or a result
+decompress' wbits p0 = go p0 =<< liftIO (Z.initInflate wbits)
+  where
+    flush inf = do
+      bs <- liftIO $ Z.flushInflate inf
+      unless (B.null bs) (yield bs)
+    go p inf = do
+      res <- lift (next p)
+      case res of
+         Left r -> return $ Right r
+         Right (bs, p') -> do
+            fromPopper =<< liftIO (Z.feedInflate inf bs)
+            flush inf
+            leftover <- liftIO $ Z.getUnusedInflate inf
+            if B.null leftover
+               then go p' inf
+               else return $ Left (yield leftover >> p')
+{-# INLINABLE decompress' #-}
+
+-- | Compress bytes flowing from a 'Producer'.
+--
+-- See the "Codec.Compression.Zlib" module for details about
+-- 'Z.CompressionLevel' and 'Z.WindowBits'.
+--
+-- @
+-- 'compress' :: 'MonadIO' m
+--          => 'Z.CompressionLevel'
+--          -> 'Z.WindowBits'
+--          -> 'Producer' 'B.ByteString' m r
+--          -> 'Producer' 'B.ByteString' m r
+-- @
+compress
+  :: MonadIO m
+  => CompressionLevel
+  -> Z.WindowBits
+  -> Proxy x' x () B.ByteString m r -- ^ Decompressed stream
+  -> Proxy x' x () B.ByteString m r -- ^ Compressed stream
+compress (CompressionLevel clevel) wbits p0 = do
+    def <- liftIO $ Z.initDeflate clevel wbits
+    r <- for p0 $ \bs -> do
+       popper <- liftIO (Z.feedDeflate def bs)
+       fromPopper popper
+    fromPopper $ Z.finishDeflate def
+    return r
+{-# INLINABLE compress #-}
+
+--------------------------------------------------------------------------------
+
+-- $ccz-re-export
+--
+-- The following are re-exported from "Codec.Compression.Zlib" for your
+-- convenience.
+
+--------------------------------------------------------------------------------
+-- Compression Levels
+
+-- | How hard should we try to compress?
+newtype CompressionLevel = CompressionLevel Int
+                         deriving (Show, Read, Eq, Ord)
+
+defaultCompression, noCompression, bestSpeed, bestCompression :: CompressionLevel
+defaultCompression = CompressionLevel (-1)
+noCompression      = CompressionLevel 0
+bestSpeed          = CompressionLevel 1
+bestCompression    = CompressionLevel 9
+
+-- | A specific compression level between 0 and 9.
+compressionLevel :: Int -> CompressionLevel
+compressionLevel n
+  | n >= 0 && n <= 9 = CompressionLevel n
+  | otherwise        = error "CompressionLevel must be in the range 0..9"
+
+windowBits :: Int -> WindowBits
+windowBits = WindowBits
+
+--------------------------------------------------------------------------------
+-- Internal stuff
+
+-- | Produce values from the given 'Z.Popper' until exhausted.
+fromPopper :: MonadIO m
+           => Z.Popper
+           -> Producer' B.ByteString m ()
+fromPopper pop = loop
+  where
+    loop = do
+      mbs <- liftIO pop
+      case mbs of
+          PRDone     -> return ()
+          PRError e  -> liftIO $ throwIO e
+          PRNext bs  -> yield bs >> loop
+{-# INLINABLE fromPopper #-}
diff --git a/test/Main.hs b/test/Main.hs
new file mode 100644
--- /dev/null
+++ b/test/Main.hs
@@ -0,0 +1,73 @@
+{-# LANGUAGE OverloadedStrings #-}
+import           Control.Arrow
+import qualified Data.ByteString.Char8 as B8
+import           Data.List
+import           Data.Ord
+import           Test.Tasty
+import           Test.Tasty.QuickCheck as QC
+import           Test.QuickCheck.Instances ()
+import           Test.Tasty.HUnit
+
+import qualified Pipes as P
+import qualified Pipes.Prelude as P
+import qualified Pipes.Zlib as PZ
+import qualified Pipes.GZip as PGZ
+
+main = defaultMain tests
+
+--------------------------------------------------------------------------------
+tests :: TestTree
+tests = testGroup "Tests" [properties, unitTests]
+
+properties :: TestTree
+properties = testGroup "Properties" [qcProps]
+
+qcProps = testGroup "(checked by QuickCheck)"
+ [ QC.testProperty "id == decompress . compress" $ \bs -> QC.ioProperty $ do
+     let pc = PZ.compress PZ.defaultCompression PZ.defaultWindowBits (P.yield bs)
+         pd = PZ.decompress PZ.defaultWindowBits pc
+     bs' <- B8.concat <$> P.toListM pd
+     return (bs QC.=== bs')
+
+ , QC.testProperty "id == decompress' . compress" $ \bs bsl -> QC.ioProperty $ do
+     let pc = PZ.compress PZ.defaultCompression PZ.defaultWindowBits (P.yield bs)
+         pd = PZ.decompress' PZ.defaultWindowBits (pc >> P.yield bsl)
+     (bs', elr) <- first B8.concat <$> P.toListM' pd
+     case elr of
+        Left pl -> do
+           bsl' <- B8.concat <$> P.toListM pl
+           return $ (bs QC.=== bs') QC..&&. (bsl QC.=== bsl')
+        Right () -> do
+           return $ (bs QC.=== bs') QC..&&. (bsl QC.=== B8.empty)
+ ]
+
+unitTests = testGroup "Unit tests"
+  [ testCase "Zlib compression default" $ do
+      let pc = PZ.compress PZ.defaultCompression PZ.defaultWindowBits
+                   (P.yield bsUncompressed)
+      bs <- B8.concat <$> P.toListM pc
+      bs @?= bsCompressedZlibDefault
+  , testCase "Zlib decompression default" $ do
+      let pd = PZ.decompress PZ.defaultWindowBits
+                   (P.yield bsCompressedZlibDefault)
+      bs <- B8.concat <$> P.toListM pd
+      bs @?= bsUncompressed
+  , testCase "GZip compression default" $ do
+      let pd = PGZ.compress PGZ.defaultCompression (P.yield bsUncompressed)
+      bs <- B8.concat <$> P.toListM pd
+      bs @?= bsCompressedGZipDefault
+  , testCase "GZip decompression default" $ do
+      let pd = PGZ.decompress (P.yield bsCompressedGZipDefault)
+      bs <- B8.concat <$> P.toListM pd
+      bs @?= bsUncompressed
+  ]
+
+bsUncompressed :: B8.ByteString
+bsUncompressed = "foo"
+
+bsCompressedZlibDefault :: B8.ByteString
+bsCompressedZlibDefault = "x\156K\203\207\a\NUL\STX\130\SOHE"
+
+bsCompressedGZipDefault :: B8.ByteString
+bsCompressedGZipDefault =
+  "\US\139\b\NUL\NUL\NUL\NUL\NUL\NUL\ETXK\203\207\a\NUL!es\140\ETX\NUL\NUL\NUL"
