diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,11 @@
 # Revision history for byteslice
 
+## 0.2.12.0 -- 2023-12-14
+
+* Add `Data.Bytes.Chunks.cons`.
+* Add `Data.Bytes.fromLazyByteString`.
+* Allow building with GHC 9.8.
+
 ## 0.2.11.1 -- 2023-07-26
 
 * Fix regression causing build failure in GHC 9.2.
diff --git a/byteslice.cabal b/byteslice.cabal
--- a/byteslice.cabal
+++ b/byteslice.cabal
@@ -1,6 +1,6 @@
 cabal-version: 2.4
 name: byteslice
-version: 0.2.11.1
+version: 0.2.12.0
 synopsis: Slicing managed and unmanaged memory
 description:
   This library provides types that allow the user to talk about a slice of
@@ -45,7 +45,7 @@
     Cstrlen
   build-depends:
     , base >=4.14 && <5
-    , bytestring >=0.10.8 && <0.12
+    , bytestring >=0.10.8 && <0.13
     , natural-arithmetic >=0.1.4
     , primitive >=0.7.4 && <0.10
     , primitive-addr >=0.1 && <0.2
diff --git a/src-new-reps/Reps.hs b/src-new-reps/Reps.hs
--- a/src-new-reps/Reps.hs
+++ b/src-new-reps/Reps.hs
@@ -1,7 +1,7 @@
+{-# language DataKinds #-}
 {-# language GADTSyntax #-}
 {-# language KindSignatures #-}
 {-# language MagicHash #-}
-{-# language TypeInType #-}
 {-# language UnboxedTuples #-}
 {-# language UnliftedNewtypes #-}
 
diff --git a/src/Data/Bytes.hs b/src/Data/Bytes.hs
--- a/src/Data/Bytes.hs
+++ b/src/Data/Bytes.hs
@@ -150,6 +150,7 @@
   , Pure.toByteString
   , Pure.pinnedToByteString
   , Pure.fromByteString
+  , Pure.fromLazyByteString
   , fromShortByteString
   , fromShortText
   , toShortByteString
diff --git a/src/Data/Bytes/Chunks.hs b/src/Data/Bytes/Chunks.hs
--- a/src/Data/Bytes/Chunks.hs
+++ b/src/Data/Bytes/Chunks.hs
@@ -21,6 +21,7 @@
   , length
   , null
     -- * Manipulate
+  , cons
   , concat
   , concatPinned
   , concatU
@@ -88,6 +89,11 @@
   -- TODO: There is a more efficient way to do this, but
   -- it is tedious.
   a == b = concat a == concat b
+
+-- | Add a byte sequence to the beginning.
+cons :: Bytes -> Chunks -> Chunks
+{-# inline cons #-}
+cons = ChunksCons
 
 -- | Are there any bytes in the chunked byte sequences?
 null :: Chunks -> Bool
diff --git a/src/Data/Bytes/Pure.hs b/src/Data/Bytes/Pure.hs
--- a/src/Data/Bytes/Pure.hs
+++ b/src/Data/Bytes/Pure.hs
@@ -31,6 +31,7 @@
   , toByteString
   , pinnedToByteString
   , fromByteString
+  , fromLazyByteString
   , unsafeDrop
   , unsafeTake
   , unsafeIndex
@@ -56,6 +57,8 @@
 
 import qualified Data.ByteString as ByteString
 import qualified Data.ByteString.Internal as ByteString
+import qualified Data.ByteString.Lazy as LBS
+import qualified Data.ByteString.Lazy.Internal as LBS
 import qualified Data.ByteString.Unsafe as ByteString
 import qualified Data.Primitive as PM
 import qualified GHC.Exts as Exts
@@ -268,6 +271,25 @@
   ) 0 len
   where
   !len = ByteString.length b
+
+-- | /O(n)/ Copy a lazy bytestring to a byte sequence.
+fromLazyByteString :: LBS.ByteString -> Bytes
+fromLazyByteString x = case LBS.length x of
+  0 -> empty
+  n64 ->
+    let n = fromIntegral n64 :: Int
+     in Bytes
+        (runByteArrayST $ unsafeIOToST $ do
+          dst@(PM.MutableByteArray dst# ) <- PM.newByteArray n
+          let loop chunks !ix = case chunks of
+                LBS.Empty -> PM.unsafeFreezeByteArray dst
+                LBS.Chunk c cs -> do
+                  let !len = ByteString.length c
+                  ByteString.unsafeUseAsCString c $ \src -> do
+                    PM.copyPtrToMutablePrimArray (PM.MutablePrimArray dst# ) ix src len
+                  loop cs (ix + len)
+          loop x 0
+        ) 0 n
 
 -- | Drop the first @n@ bytes from the argument. Precondition: @n ≤ len@
 unsafeDrop :: Int -> Bytes -> Bytes
diff --git a/src/Data/Bytes/Text/Utf8.hs b/src/Data/Bytes/Text/Utf8.hs
--- a/src/Data/Bytes/Text/Utf8.hs
+++ b/src/Data/Bytes/Text/Utf8.hs
@@ -8,6 +8,9 @@
 #if MIN_VERSION_text(2,0,0)
   , fromText
 #endif
+#if MIN_VERSION_text(2,1,0)
+  , toText
+#endif
   ) where
 
 import Data.Bytes.Types (Bytes(Bytes))
@@ -20,6 +23,10 @@
 import qualified Data.Text.Internal as I
 import qualified Data.Text.Array as A
 
+#if MIN_VERSION_text(2,1,0)
+import qualified Data.Text.Internal.Validate
+#endif
+
 -- | Encode 'ShortText' using UTF-8. Since 'ShortText' is backed by a UTF-8
 -- byte sequence, this does not perform a copy.
 fromShortText :: ShortText -> Bytes
@@ -33,10 +40,23 @@
 toShortText !b = TS.fromShortByteString (Bytes.toShortByteString b)
 
 #if MIN_VERSION_text(2,0,0)
--- | Encode 'Text' using 'UTF-8'. Only available when building with
+-- | Encode 'Text' using @UTF-8@. Only available when building with
 -- @text-2.0@ and newer. Since 'Text' is backed by a UTF-8
 -- byte sequence, this does not perform a copy.
 fromText :: Text -> Bytes
 {-# inline fromText #-}
 fromText (I.Text (A.ByteArray b) off len) = Bytes (ByteArray b) off len
+#endif
+
+#if MIN_VERSION_text(2,1,0)
+-- | Attempt to interpret byte sequence as @UTF-8@ encoded 'Text'.
+-- Only available when building with @text-2.1@ and newer. Since
+-- 'Text' is backed by a UTF-8 byte sequence, this does not perform a
+-- copy.
+toText :: Bytes -> Maybe Text
+{-# inline toText #-}
+toText (Bytes b@(ByteArray b0) off len) =
+  if Data.Text.Internal.Validate.isValidUtf8ByteArray b off len
+    then Just (I.Text (A.ByteArray b0) off len)
+    else Nothing
 #endif
