diff --git a/bench/Main.hs b/bench/Main.hs
--- a/bench/Main.hs
+++ b/bench/Main.hs
@@ -1,16 +1,27 @@
-{-# LANGUAGE NamedFieldPuns #-}
+{-# OPTIONS -Wno-orphans #-}
 
-import           RON.Internal.Prelude
+{-# LANGUAGE DeriveAnyClass #-}
+{-# LANGUAGE NamedFieldPuns #-}
+{-# LANGUAGE StandaloneDeriving #-}
 
-import           Control.DeepSeq (force)
+import           Control.DeepSeq (NFData, force)
 import           Control.Exception (evaluate)
+import           Control.Monad (void)
 import           Criterion (bench, nf)
 import           Criterion.Main (defaultConfig, defaultMainWith)
 import           Criterion.Types (timeLimit)
 
 import           RON.Text (parseWireFrames, serializeWireFrames)
-import           RON.Types (Op (..), RawOp (..), WireChunk (Raw))
+import           RON.Types (Atom, Op (..), RawOp (..), UUID, WireChunk (Raw),
+                            WireReducedChunk)
 import qualified RON.UUID as UUID
+
+deriving instance NFData Atom
+deriving instance NFData Op
+deriving instance NFData RawOp
+deriving instance NFData UUID
+deriving instance NFData WireChunk
+deriving instance NFData WireReducedChunk
 
 main :: IO ()
 main = do
diff --git a/lib/RON/Base64.hs b/lib/RON/Base64.hs
--- a/lib/RON/Base64.hs
+++ b/lib/RON/Base64.hs
@@ -29,9 +29,8 @@
 import qualified Data.ByteString.Lazy as BSL
 import           Data.Char (ord)
 
-import           RON.Internal.Word (Word4, Word6 (W6), Word60,
-                                    leastSignificant4, leastSignificant6,
-                                    leastSignificant60, safeCast)
+import           RON.Util.Word (Word4, Word6 (W6), Word60, leastSignificant4,
+                                leastSignificant6, leastSignificant60, safeCast)
 
 -- | Base64 alphabet
 alphabet :: ByteString
diff --git a/lib/RON/Binary/Parse.hs b/lib/RON/Binary/Parse.hs
--- a/lib/RON/Binary/Parse.hs
+++ b/lib/RON/Binary/Parse.hs
@@ -28,7 +28,7 @@
 import           Data.ZigZag (zzDecode64)
 
 import           RON.Binary.Types (Desc (..), Size, descIsOp)
-import           RON.Internal.Word (safeCast)
+import           RON.Util.Word (safeCast)
 import           RON.Types (Atom (AFloat, AInteger, AString, AUuid), Op (..),
                             OpTerm (THeader, TQuery, TRaw, TReduced),
                             RawOp (..), UUID (UUID),
diff --git a/lib/RON/Binary/Serialize.hs b/lib/RON/Binary/Serialize.hs
--- a/lib/RON/Binary/Serialize.hs
+++ b/lib/RON/Binary/Serialize.hs
@@ -23,11 +23,11 @@
 import           Data.ZigZag (zzEncode)
 
 import           RON.Binary.Types (Desc (..), Size, descIsOp)
-import           RON.Internal.Word (Word4, b0000, leastSignificant4, safeCast)
 import           RON.Types (Atom (AFloat, AInteger, AString, AUuid), Op (..),
                             RawOp (..), UUID (UUID),
                             WireChunk (Query, Raw, Value), WireFrame,
                             WireReducedChunk (..))
+import           RON.Util.Word (Word4, b0000, leastSignificant4, safeCast)
 
 -- | Serialize a frame
 serialize :: WireFrame -> Either String ByteStringL
diff --git a/lib/RON/Data.hs b/lib/RON/Data.hs
--- a/lib/RON/Data.hs
+++ b/lib/RON/Data.hs
@@ -8,19 +8,20 @@
 {-# LANGUAGE TypeApplications #-}
 
 -- | Typed and untyped RON tools
-module RON.Data
-    ( Reducible (..)
-    , Replicated (..)
-    , ReplicatedAsObject (..)
-    , ReplicatedAsPayload (..)
-    , fromRon
-    , newRon
-    , objectEncoding
-    , payloadEncoding
-    , reduceObject
-    , reduceStateFrame
-    , reduceWireFrame
-    ) where
+module RON.Data (
+    Reducible (..),
+    Replicated (..),
+    ReplicatedAsObject (..),
+    ReplicatedAsPayload (..),
+    fromRon,
+    mkStateChunk,
+    newRon,
+    objectEncoding,
+    payloadEncoding,
+    reduceObject,
+    reduceStateFrame,
+    reduceWireFrame,
+) where
 
 import           RON.Internal.Prelude
 
diff --git a/lib/RON/Data/RGA.hs b/lib/RON/Data/RGA.hs
--- a/lib/RON/Data/RGA.hs
+++ b/lib/RON/Data/RGA.hs
@@ -37,8 +37,8 @@
 import           RON.Data.Internal
 import           RON.Event (ReplicaClock, advanceToUuid, getEventUuid,
                             getEventUuids)
-import           RON.Internal.Word (pattern B11, ls60)
 import           RON.Types (Object (..), Op (..), StateChunk (..), UUID)
+import           RON.Util.Word (pattern B11, ls60)
 import           RON.UUID (pattern Zero, uuidVersion)
 import qualified RON.UUID as UUID
 
diff --git a/lib/RON/Data/Time.hs b/lib/RON/Data/Time.hs
--- a/lib/RON/Data/Time.hs
+++ b/lib/RON/Data/Time.hs
@@ -28,4 +28,4 @@
 
 -- | RON-Schema type for 'Day'
 day :: RonType
-day = opaqueAtoms def{oaHaskellType = Just "Day"}
+day = opaqueAtoms "Day" def{oaHaskellType = Just "Day"}
diff --git a/lib/RON/Epoch.hs b/lib/RON/Epoch.hs
--- a/lib/RON/Epoch.hs
+++ b/lib/RON/Epoch.hs
@@ -1,8 +1,9 @@
 {-# LANGUAGE GeneralizedNewtypeDeriving #-}
-{-# LANGUAGE TypeApplications #-}
 
 module RON.Epoch (
     EpochClock,
+    decode,
+    encode,
     getCurrentEpochTime,
     localEpochTimeFromUnix,
     runEpochClock,
@@ -12,13 +13,16 @@
 import           Control.Monad.IO.Class (MonadIO)
 import           Control.Monad.Reader (ReaderT (ReaderT), reader, runReaderT)
 import           Data.IORef (IORef, atomicModifyIORef', newIORef)
-import           Data.Time.Clock.POSIX (getPOSIXTime)
+import           Data.Ratio ((%))
+import           Data.Time (UTCTime)
+import           Data.Time.Clock.POSIX (POSIXTime, getPOSIXTime,
+                                        posixSecondsToUTCTime)
 import           Data.Word (Word64)
 
 import           RON.Event (EpochEvent (EpochEvent), EpochTime,
                             LocalTime (TEpoch), ReplicaClock, ReplicaId,
                             advance, getEvents, getPid)
-import           RON.Internal.Word (leastSignificant60, ls60, word60add)
+import           RON.Util.Word (leastSignificant60, ls60, safeCast, word60add)
 
 -- | Real epoch clock.
 -- Uses kind of global variable to ensure strict monotonicity.
@@ -34,13 +38,11 @@
     getEvents n0 = EpochClock $ ReaderT $ \(pid, timeVar) -> do
         let n = max n0 $ ls60 1
         realTime <- getCurrentEpochTime
-        timeRangeStart <- atomicModifyIORef' timeVar $ \timeCur ->
-            let timeRangeStart = max realTime $ succ timeCur
-            in (timeRangeStart `word60add` pred n, timeRangeStart)
-        pure
-            [ EpochEvent t pid
-            | t <- [timeRangeStart .. timeRangeStart `word60add` pred n]
-            ]
+        (begin, end) <- atomicModifyIORef' timeVar $ \timeCur -> let
+            begin = max realTime $ succ timeCur
+            end   = begin `word60add` pred n
+            in (end, (begin, end))
+        pure [EpochEvent t pid | t <- [begin .. end]]
 
 -- | Run 'EpochClock' action with explicit time variable.
 runEpochClock :: ReplicaId -> IORef EpochTime -> EpochClock a -> IO a
@@ -57,20 +59,29 @@
 -- | Get current time in 'EpochTime' format (with 100 ns resolution).
 -- Monotonicity is not guaranteed.
 getCurrentEpochTime :: IO EpochTime
-getCurrentEpochTime
-    =   epochTimeFromUnix @Word64
-    .   round
-    .   (* 10000000)
-    <$> getPOSIXTime
+getCurrentEpochTime = encode <$> getPOSIXTime
 
 -- | Convert unix time in hundreds of milliseconds to RFC 4122 time.
-epochTimeFromUnix :: Integral int => int -> EpochTime
-epochTimeFromUnix
-    =   leastSignificant60
-    .   (+ 0x01B21DD213814000)
-        -- the difference between Unix epoch and UUID epoch;
-        -- the constant is taken from RFC 4122
+epochTimeFromUnix :: Word64 -> EpochTime
+epochTimeFromUnix = leastSignificant60 . (+ epochDiff)
 
+-- The difference between Unix epoch and UUID epoch;
+-- the constant is taken from RFC 4122
+epochDiff :: Word64
+epochDiff = 0x01B21DD213814000
+
 -- | Convert unix time in hundreds of milliseconds to RFC 4122 time.
-localEpochTimeFromUnix :: Integral int => int -> LocalTime
+localEpochTimeFromUnix :: Word64 -> LocalTime
 localEpochTimeFromUnix = TEpoch . epochTimeFromUnix
+
+-- | Decode date and time from UUID epoch timestamp
+decode :: EpochTime -> UTCTime
+decode
+    = posixSecondsToUTCTime
+    . realToFrac
+    . (% 10000000)
+    . subtract epochDiff
+    . safeCast
+
+encode :: POSIXTime -> EpochTime
+encode = epochTimeFromUnix . round . (* 10000000)
diff --git a/lib/RON/Event.hs b/lib/RON/Event.hs
--- a/lib/RON/Event.hs
+++ b/lib/RON/Event.hs
@@ -34,13 +34,13 @@
 import           Data.Bits (shiftL, shiftR, (.|.))
 import           Data.Hashable (Hashable, hashUsing, hashWithSalt)
 
-import           RON.Internal.Word (pattern B00, pattern B01, pattern B10,
-                                    pattern B11, Word12, Word16, Word2, Word24,
-                                    Word32, Word6, Word60, Word64, Word8,
-                                    leastSignificant12, leastSignificant2,
-                                    leastSignificant24, leastSignificant4,
-                                    leastSignificant6, ls12, ls24, ls6, ls60,
-                                    safeCast)
+import           RON.Util.Word (pattern B00, pattern B01, pattern B10,
+                                pattern B11, Word12, Word16, Word2, Word24,
+                                Word32, Word6, Word60, Word64, Word8,
+                                leastSignificant12, leastSignificant2,
+                                leastSignificant24, leastSignificant4,
+                                leastSignificant6, ls12, ls24, ls6, ls60,
+                                safeCast)
 import           RON.UUID (UUID, UuidFields (UuidFields), uuidOrigin, uuidValue,
                            uuidVariant, uuidVariety, uuidVersion)
 import qualified RON.UUID as UUID
diff --git a/lib/RON/Event/Simulation.hs b/lib/RON/Event/Simulation.hs
--- a/lib/RON/Event/Simulation.hs
+++ b/lib/RON/Event/Simulation.hs
@@ -23,9 +23,9 @@
 import           Data.HashMap.Strict (HashMap)
 import qualified Data.HashMap.Strict as HM
 
-import           RON.Event (EpochEvent (EpochEvent), ReplicaClock, ReplicaId,
-                            advance, getEvents, getPid)
-import           RON.Internal.Word (Word60, ls60, word60add)
+import           RON.Event (EpochEvent (EpochEvent), ReplicaClock,
+                            ReplicaId (ReplicaId), advance, getEvents, getPid)
+import           RON.Util.Word (Word60, ls60, safeCast, word60add)
 
 -- | Lamport clock simulation. Key is 'ReplicaId'.
 -- Non-present value is equivalent to (0, initial).
@@ -53,10 +53,14 @@
         rid <- ask
         (t0, t1) <-
             lift $ NetworkSim $ state $ \replicaStates -> let
-                t0 = HM.lookupDefault (ls60 1) rid replicaStates
+                t0orig = HM.lookupDefault (ls60 0) rid replicaStates
+                ReplicaId _ r = rid
+                randomLeap =
+                    ls60 $ (safeCast t0orig + safeCast n + safeCast r) `mod` 41
+                t0 = t0orig `word60add` randomLeap
                 t1 = t0 `word60add` n
                 in ((t0, t1), HM.insert rid t1 replicaStates)
-        pure [EpochEvent t rid | t <- [t0 .. t1]]
+        pure [EpochEvent t rid | t <- [succ t0 .. t1]]
       where
         n = max n' (ls60 1)
 
diff --git a/lib/RON/Internal/Word.hs b/lib/RON/Internal/Word.hs
deleted file mode 100644
--- a/lib/RON/Internal/Word.hs
+++ /dev/null
@@ -1,193 +0,0 @@
-{-# LANGUAGE BinaryLiterals #-}
-{-# LANGUAGE GeneralizedNewtypeDeriving #-}
-{-# LANGUAGE MultiParamTypeClasses #-}
-{-# LANGUAGE PatternSynonyms #-}
-{-# LANGUAGE TypeApplications #-}
-
-module RON.Internal.Word
-    (
-    -- * Word2
-      Word2
-    , b00, b01, b10, b11
-    , pattern B00, pattern B01, pattern B10, pattern B11
-    , leastSignificant2
-    -- * Word4
-    , Word4
-    , b0000, b0001, b0010, b0011, b0100, b0101, b0110, b0111
-    , b1000, b1001, b1010, b1011, b1100, b1101, b1110, b1111
-    , pattern B0000
-    , leastSignificant4
-    -- * Word6
-    , Word6 (..)
-    , leastSignificant6
-    , ls6
-    -- * Word8
-    , Word8
-    -- * Word12
-    , Word12
-    , leastSignificant12
-    , ls12
-    -- * Word16
-    , Word16
-    -- * Word24
-    , Word24
-    , leastSignificant24
-    , ls24
-    -- * Word32
-    , Word32
-    -- * Word60
-    , Word60
-    , leastSignificant60
-    , ls60
-    , toWord60
-    , word60add
-    -- * Word64
-    , Word64
-    -- * SafeCast
-    , SafeCast (..)
-    ) where
-
-import           Data.Bits ((.&.))
-import           Data.Coerce (coerce)
-import           Data.Fixed (Fixed, HasResolution)
-import           Data.Hashable (Hashable, hashUsing, hashWithSalt)
-import           Data.Word (Word16, Word32, Word64, Word8)
-
-newtype Word2 = W2 Word8
-    deriving (Eq, Ord, Show)
-
-b00, b01, b10, b11 :: Word2
-b00 = W2 0b00
-b01 = W2 0b01
-b10 = W2 0b10
-b11 = W2 0b11
-
-pattern B00 :: Word2
-pattern B00 = W2 0b00
-pattern B01 :: Word2
-pattern B01 = W2 0b01
-pattern B10 :: Word2
-pattern B10 = W2 0b10
-pattern B11 :: Word2
-pattern B11 = W2 0b11
-{-# COMPLETE B00, B01, B10, B11 #-}
-
--- | 'Word2' smart constructor dropping upper bits
-leastSignificant2 :: Integral integral => integral -> Word2
-leastSignificant2 = W2 . (0b11 .&.) . fromIntegral
-
-newtype Word4 = W4 Word8
-    deriving (Eq, Ord, Show)
-
-b0000, b0001, b0010, b0011, b0100, b0101, b0110, b0111 :: Word4
-b1000, b1001, b1010, b1011, b1100, b1101, b1110, b1111 :: Word4
-b0000 = W4 0b0000
-b0001 = W4 0b0001
-b0010 = W4 0b0010
-b0011 = W4 0b0011
-b0100 = W4 0b0100
-b0101 = W4 0b0101
-b0110 = W4 0b0110
-b0111 = W4 0b0111
-b1000 = W4 0b1000
-b1001 = W4 0b1001
-b1010 = W4 0b1010
-b1011 = W4 0b1011
-b1100 = W4 0b1100
-b1101 = W4 0b1101
-b1110 = W4 0b1110
-b1111 = W4 0b1111
-
-pattern B0000 :: Word4
-pattern B0000 = W4 0b0000
-
--- | 'Word4' smart constructor dropping upper bits
-leastSignificant4 :: Integral integral => integral -> Word4
-leastSignificant4 = W4 . (0xF .&.) . fromIntegral
-
-newtype Word6 = W6 Word8
-    deriving (Eq, Ord, Show)
-
--- | 'Word6' smart constructor dropping upper bits
-leastSignificant6 :: Integral integral => integral -> Word6
-leastSignificant6 = W6 . (0x3F .&.) . fromIntegral
-
--- | 'leastSignificant6' specialized for 'Word8'
-ls6 :: Word8 -> Word6
-ls6 = W6 . (0x3F .&.)
-
-newtype Word12 = W12 Word16
-    deriving (Eq, Ord, Show)
-
--- | 'Word12' smart constructor dropping upper bits
-leastSignificant12 :: Integral integral => integral -> Word12
-leastSignificant12 = W12 . (0xFFF .&.) . fromIntegral
-
--- | 'leastSignificant12' specialized for 'Word16'
-ls12 :: Word16 -> Word12
-ls12 = W12 . (0xFFF .&.)
-
-newtype Word24 = W24 Word32
-    deriving (Eq, Ord, Show)
-
--- | 'Word24' smart constructor dropping upper bits
-leastSignificant24 :: Integral integral => integral -> Word24
-leastSignificant24 = W24 . (0xFFFFFF .&.) . fromIntegral
-
--- | 'leastSignificant24' specialized for 'Word32'
-ls24 :: Word32 -> Word24
-ls24 = W24 . (0xFFF .&.)
-
-newtype Word60 = W60 Word64
-    deriving (Enum, Eq, Ord, Show)
-
-instance Bounded Word60 where
-    minBound = W60 0
-    maxBound = W60 0xFFFFFFFFFFFFFFF
-
-instance Hashable Word60 where
-    hashWithSalt = hashUsing @Word64 coerce
-
--- | 'Word60' smart constructor dropping upper bits
-leastSignificant60 :: Integral integral => integral -> Word60
-leastSignificant60 = W60 . (0xFFFFFFFFFFFFFFF .&.) . fromIntegral
-
--- | 'leastSignificant60' specialized for 'Word64'
-ls60 :: Word64 -> Word60
-ls60 = W60 . (0xFFFFFFFFFFFFFFF .&.)
-
--- | 'Word60' smart constructor checking domain
-toWord60 :: Word64 -> Maybe Word60
-toWord60 w
-    | w < 0x1000000000000000 = Just $ W60 w
-    | otherwise              = Nothing
-
-word60add :: Word60 -> Word60 -> Word60
-word60add (W60 a) (W60 b) = leastSignificant60 $ a + b
-
-class SafeCast v w where
-    safeCast :: v -> w
-
-instance SafeCast Word2  Int     where safeCast = fromIntegral @Word8 . coerce
-instance SafeCast Word2  Word4   where safeCast = coerce
-instance SafeCast Word2  Word8   where safeCast = coerce
-instance SafeCast Word2  Word64  where safeCast = fromIntegral @Word8 . coerce
-instance SafeCast Word4  Int     where safeCast = fromIntegral @Word8 . coerce
-instance SafeCast Word4  Word64  where safeCast = fromIntegral @Word8 . coerce
-instance SafeCast Word4  Word8   where safeCast = coerce
-instance SafeCast Word6  Int     where safeCast = fromIntegral @Word8 . coerce
-instance SafeCast Word6  Word8   where safeCast = coerce
-instance SafeCast Word6  Word60  where safeCast = coerce @Word64
-                                                . fromIntegral @Word8 . coerce
-instance SafeCast Word6  Word64  where safeCast = fromIntegral @Word8 . coerce
-instance SafeCast Word8  Word32  where safeCast = fromIntegral
-instance SafeCast Word8  Word64  where safeCast = fromIntegral
-instance SafeCast Word12 Word64  where safeCast = fromIntegral @Word16 . coerce
-instance SafeCast Word24 Word64  where safeCast = fromIntegral @Word32 . coerce
-instance SafeCast Word24 Word32  where safeCast = coerce
-instance SafeCast Word60 Word64  where safeCast = coerce
-instance SafeCast Word64 Integer where safeCast = fromIntegral
-
--- Fixed are Integers inside, so have arbitrary magnitude
-instance HasResolution e => SafeCast Word64 (Fixed e) where
-    safeCast = fromIntegral
diff --git a/lib/RON/Schema.hs b/lib/RON/Schema.hs
--- a/lib/RON/Schema.hs
+++ b/lib/RON/Schema.hs
@@ -2,33 +2,34 @@
 {-# LANGUAGE NamedFieldPuns #-}
 {-# LANGUAGE OverloadedStrings #-}
 
-module RON.Schema
-    ( Declaration (..)
-    , Field (..)
-    , FieldAnnotations (..)
-    , OpaqueAnnotations (..)
-    , RonType (..)
-    , Schema
-    , StructAnnotations (..)
-    , StructLww (..)
-    , TAtom (..)
-    , TComposite (..)
-    , TObject (..)
-    , TOpaque (..)
-    , atomInteger
-    , atomString
-    , boole
-    , char
-    , def
-    , field
-    , opaqueAtoms
-    , opaqueObject
-    , option
-    , orSet
-    , rgaString
-    , structLww
-    , versionVector
-    ) where
+module RON.Schema (
+    CaseTransform (..),
+    Declaration (..),
+    Field (..),
+    FieldAnnotations (..),
+    Opaque (..),
+    OpaqueAnnotations (..),
+    RonType (..),
+    Schema,
+    StructAnnotations (..),
+    StructLww (..),
+    TAtom (..),
+    TComposite (..),
+    TObject (..),
+    atomInteger,
+    atomString,
+    boole,
+    char,
+    def,
+    field,
+    opaqueAtoms,
+    opaqueObject,
+    option,
+    orSet,
+    rgaString,
+    structLww,
+    versionVector,
+) where
 
 import           RON.Internal.Prelude
 
@@ -41,7 +42,7 @@
     = TAtom      TAtom
     | TComposite TComposite
     | TObject    TObject
-    | TOpaque    TOpaque
+    | TOpaque    Opaque
     deriving (Show)
 
 newtype TComposite
@@ -63,11 +64,16 @@
     deriving (Show)
 
 data StructAnnotations = StructAnnotations
-    {saHaskellDeriving :: Set Text, saHaskellFieldPrefix :: Text}
+    { saHaskellFieldPrefix        :: Text
+    , saHaskellFieldCaseTransform :: Maybe CaseTransform
+    }
     deriving (Show)
 
-instance Default StructAnnotations where def = StructAnnotations def ""
+data CaseTransform = TitleCase
+    deriving (Show)
 
+instance Default StructAnnotations where def = StructAnnotations "" Nothing
+
 data Field = Field{fieldType :: RonType, fieldAnnotations :: FieldAnnotations}
     deriving (Show)
 
@@ -80,7 +86,7 @@
 instance Default FieldAnnotations where
     def = FieldAnnotations
 
-newtype Declaration = DStructLww StructLww
+data Declaration = DOpaque Opaque | DStructLww StructLww
 
 type Schema = [Declaration]
 
@@ -88,20 +94,23 @@
     deriving (Default, Show)
 
 char :: RonType
-char = opaqueAtoms def{oaHaskellType = Just "Char"}
+char = opaqueAtoms "Char" def{oaHaskellType = Just "Char"}
 
 rgaString :: RonType
 rgaString = TObject $ TRga char
 
-data TOpaque =
-    Opaque{opaqueIsObject :: Bool, opaqueAnnotations :: OpaqueAnnotations}
+data Opaque = Opaque
+    { opaqueIsObject    :: Bool
+    , opaqueName        :: Text
+    , opaqueAnnotations :: OpaqueAnnotations
+    }
     deriving (Show)
 
-opaqueObject :: OpaqueAnnotations -> RonType
-opaqueObject = TOpaque . Opaque True
+opaqueObject :: Text -> OpaqueAnnotations -> RonType
+opaqueObject name = TOpaque . Opaque True name
 
-opaqueAtoms :: OpaqueAnnotations -> RonType
-opaqueAtoms = TOpaque . Opaque False
+opaqueAtoms :: Text -> OpaqueAnnotations -> RonType
+opaqueAtoms name = TOpaque . Opaque False name
 
 option :: RonType -> RonType
 option = TComposite . TOption
@@ -122,4 +131,4 @@
 versionVector = TObject TVersionVector
 
 boole :: RonType
-boole = opaqueAtoms def{oaHaskellType = Just "Bool"}
+boole = opaqueAtoms "Boole" def{oaHaskellType = Just "Bool"}
diff --git a/lib/RON/Schema/EDN.hs b/lib/RON/Schema/EDN.hs
new file mode 100644
--- /dev/null
+++ b/lib/RON/Schema/EDN.hs
@@ -0,0 +1,257 @@
+{-# LANGUAGE LambdaCase #-}
+{-# LANGUAGE NamedFieldPuns #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module RON.Schema.EDN (parseSchema) where
+
+import           RON.Internal.Prelude
+
+import           Control.Monad.State.Strict (StateT, evalStateT, get, gets, put)
+import           Control.Monad.Trans (MonadTrans, lift)
+import           Control.Monad.Trans.Identity (runIdentityT)
+import           Data.Attoparsec.Lazy (Result (Done))
+import qualified Data.ByteString as BS
+import qualified Data.ByteString.Lazy.Char8 as BSLC
+import           Data.Char (isSpace)
+import           Data.EDN (Tagged (NoTag, Tagged), Value (List, Map, Symbol),
+                           (.!=), (.:?))
+import           Data.EDN.Encode (fromTagged)
+import           Data.EDN.Parser (parseBSL)
+import           Data.EDN.Types (EDNList, EDNMap)
+import           Data.EDN.Types.Class (Parser, parseEither, typeMismatch)
+import qualified Data.Map.Strict as Map
+import qualified Data.Text as Text
+import qualified Data.Text.Encoding as Text
+import qualified Data.Text.Lazy as TextL
+import           Data.Text.Lazy.Builder (toLazyText)
+import qualified Data.Text.Lazy.Encoding as TextL
+
+import           RON.Schema
+
+newtype Env = Env {knownTypes :: Map Text RonType}
+    deriving (Show)
+
+startEnv :: Env
+startEnv = Env
+    { knownTypes = Map.fromList
+        [ ("AtomInteger",   atomInteger)
+        , ("AtomString",    atomString)
+        , ("RgaString",     rgaString)
+        , ("VersionVector", versionVector)
+        ]
+    }
+
+type Parser' = StateT Env Parser
+
+parseDeclaration :: Tagged Value -> Parser' Declaration
+parseDeclaration = withNoTag $ withList "declaration" $ \case
+    func : args -> do
+        func' <- withNoTag pure func
+        withSymbol "declaration name symbol" (go args) func'
+    [] -> fail "empty declaration"
+  where
+    go args = withNoPrefix "declaration name" $ \case
+        "opaque"     -> DOpaque    <$> parseOpaque    args
+        "struct_lww" -> DStructLww <$> parseStructLww args
+        name         -> fail $ "unknown declaration " ++ decodeUtf8 name
+
+parseOpaque :: EDNList -> Parser' Opaque
+parseOpaque code = do
+    opaque@Opaque{opaqueName} <- case code of
+        kind' : name : annotations -> do
+            kind <- parseKind kind'
+            case kind of
+                "atoms"  -> go False
+                "object" -> go True
+                _        -> fail "opaque kind must be either atoms or object"
+          where
+            go isObject =
+                Opaque isObject <$> parseName name <*> parseAnnotations
+            parseKind =
+                withNoTag $
+                withSymbol "opaque kind symbol" $
+                withNoPrefix "opaque kind" pure
+            parseName =
+                withNoTag $
+                withSymbol "opaque name symbol" $
+                withNoPrefix "opaque name" $
+                pure . Text.decodeUtf8
+            parseAnnotations = case annotations of
+                [] -> pure def
+                _  -> fail "opaque annotations are not implemented yet"
+        _ -> fail
+            "Expected declaration in the form\
+            \ (opaque <kind:symbol> <name:symbol> <annotations>...)"
+    env@Env{knownTypes} <- get
+    case Map.lookup opaqueName knownTypes of
+        Nothing ->
+            put env { knownTypes =
+                        Map.insert opaqueName (TOpaque opaque) knownTypes
+                    }
+        Just _ ->
+            fail $ "duplicate declaration of type " ++ Text.unpack opaqueName
+    pure opaque
+
+parseStructLww :: EDNList -> Parser' StructLww
+parseStructLww code = do
+    struct@StructLww{structName} <- case code of
+        name : body -> do
+            let (annotations, fields) = span isTagged body
+            StructLww
+                <$> parseName name
+                <*> parseFields fields
+                <*> parseAnnotations annotations
+        [] -> fail
+            "Expected declaration in the form\
+            \ (struct_lww <name:symbol> <annotations>... <fields>...)"
+    env@Env{knownTypes} <- get
+    case Map.lookup structName knownTypes of
+        Nothing ->
+            put env { knownTypes =
+                        Map.insert structName (structLww struct) knownTypes
+                    }
+        Just _ ->
+            fail $ "duplicate declaration of type " ++ Text.unpack structName
+    pure struct
+
+  where
+
+    parseName =
+        withNoTag $
+        withSymbol "struct_lww name symbol" $
+        withNoPrefix "struct_lww name" $
+        pure . Text.decodeUtf8
+
+    parseFields = \case
+        [] -> pure mempty
+        nameAsTagged : typeAsTagged : cont -> do
+            name <- parseFieldName nameAsTagged
+            typ  <- parseType typeAsTagged
+            Map.insert name (Field typ FieldAnnotations) <$> parseFields cont
+        [f] -> fail $ "field " ++ showEdn f ++ " must have type"
+      where
+        parseFieldName =
+            withNoTag $
+            withSymbol "struct_lww field name symbol" $
+            withNoPrefix "struct_lww field name" $
+            pure . Text.decodeUtf8
+
+    parseAnnotations annTaggedValues = do
+        annValues <- traverse unwrapTag annTaggedValues
+        case lookup "haskell" annValues of
+            Nothing -> pure def
+            Just annValue ->
+                withMap "struct_lww haskell annotations map" go annValue
+      where
+        unwrapTag = \case
+            Tagged value prefix tag -> let
+                name | BS.null prefix = tag | otherwise = prefix <> "/" <> tag
+                in pure (name, value)
+            NoTag _ -> fail "annotation must be a tagged value"
+        go m = lift $
+            StructAnnotations
+            <$>  m .:? Symbol "" "field_prefix" .!= ""
+            <*> (m .:? Symbol "" "field_case" >>= traverse parseCaseTransform)
+
+parseCaseTransform :: Value -> Parser CaseTransform
+parseCaseTransform =
+    (runIdentityT .) $
+    withSymbol "case transformation symbol" $
+    withNoPrefix "case transformation" $ \case
+        "title" -> pure TitleCase
+        _       -> fail "unknown case transformation"
+
+parseSchema :: Monad m => String -> m Schema
+parseSchema string = either fail pure $ do
+    values <- parseEdnStream $ encodeUtf8L string
+    parseEither ((`evalStateT` startEnv) . traverse parseDeclaration) values
+
+parseEdnStream :: ByteStringL -> Either String EDNList
+parseEdnStream input
+    | BSLC.all isSpace input = pure []
+    | otherwise              =
+        case parseBSL input of
+            Done rest value -> (value :) <$> parseEdnStream rest
+            failure         -> Left $ show failure
+
+parseType :: Tagged Value -> Parser' RonType
+parseType = withNoTag $ \case
+    Symbol "" name ->
+        gets (Map.lookup (Text.decodeUtf8 name) . knownTypes) >>= \case
+            Nothing  -> fail $ "unknown type " ++ decodeUtf8 name
+            Just typ -> pure typ
+    Symbol _ _ -> fail "types must not be prefixed"
+    List expr -> evalType expr
+    value -> lift $ typeMismatch "type symbol or expression" value
+
+evalType :: EDNList -> Parser' RonType
+evalType = \case
+    []  -> fail "empty type expression"
+    [a] -> parseType a
+    func : args -> applyType func =<< traverse parseType args
+
+applyType :: Tagged Value -> [RonType] -> Parser' RonType
+applyType func args =
+    withNoTag
+        (withSymbol "parametric type symbol" $
+            withNoPrefix "parametric type" go)
+        func
+  where
+
+    go = \case
+        "Option" -> apply "Option" option
+        "ORSet"  -> apply "ORSet" orSet
+        name     -> fail $ "unknown parametric type " ++ decodeUtf8 name
+
+    apply name wrapper = case args of
+        [a] -> pure $ wrapper a
+        _  -> fail $ name ++ " expects 1 argument, got " ++ show (length args)
+
+-- * Parser helpers
+
+withNoPrefix
+    :: Monad m
+    => String -> (ByteString -> m a) -> ByteString -> ByteString -> m a
+withNoPrefix ctx f prefix name = do
+    unless (prefix == "") $ fail $ ctx ++ ": empty prefix expected"
+    f name
+
+withList :: String -> (EDNList -> Parser' a) -> Value -> Parser' a
+withList expected f = \case
+    List list -> f list
+    value     -> lift $ typeMismatch expected value
+
+withMap :: String -> (EDNMap -> Parser' a) -> Value -> Parser' a
+withMap expected f = \case
+    Map m -> f m
+    value -> lift $ typeMismatch expected value
+
+withNoTag :: Monad m => (Value -> m a) -> Tagged Value -> m a
+withNoTag f = \case
+    NoTag value         -> f value
+    Tagged _ prefix tag -> fail
+        $ "when expecting a non-tagged value, encountered tag "
+        ++ decodeUtf8 prefix ++ "/" ++ decodeUtf8 tag ++ " instead"
+
+withSymbol
+    :: MonadTrans t
+    => String -> (ByteString -> ByteString -> t Parser a) -> Value -> t Parser a
+withSymbol expected f = \case
+    Symbol prefix symbol -> f prefix symbol
+    value                -> lift $ typeMismatch expected value
+
+-- * ByteString helpers
+
+decodeUtf8 :: ByteString -> String
+decodeUtf8 = Text.unpack . Text.decodeUtf8
+
+encodeUtf8L :: String -> ByteStringL
+encodeUtf8L = TextL.encodeUtf8 . TextL.pack
+
+isTagged :: Tagged a -> Bool
+isTagged = \case
+    NoTag {} -> False
+    Tagged{} -> True
+
+showEdn :: Tagged Value -> String
+showEdn = TextL.unpack . toLazyText . fromTagged
diff --git a/lib/RON/Schema/TH.hs b/lib/RON/Schema/TH.hs
--- a/lib/RON/Schema/TH.hs
+++ b/lib/RON/Schema/TH.hs
@@ -7,6 +7,7 @@
 module RON.Schema.TH(
     module X,
     mkReplicated,
+    mkReplicated',
 ) where
 
 import           Prelude hiding (read)
@@ -16,12 +17,15 @@
 import           Control.Monad.Except (MonadError)
 import           Control.Monad.State.Strict (MonadState, StateT)
 import qualified Data.ByteString.Char8 as BSC
+import           Data.Char (toTitle)
 import qualified Data.Map.Strict as Map
 import qualified Data.Text as Text
 import           Language.Haskell.TH (Exp (VarE), bindS, conE, conP, conT,
                                       dataD, doE, letS, listE, noBindS, recC,
                                       recConE, sigD, varE, varP, varT)
 import qualified Language.Haskell.TH as TH
+import           Language.Haskell.TH.Quote (QuasiQuoter (QuasiQuoter), quoteDec,
+                                            quoteExp, quotePat, quoteType)
 import           Language.Haskell.TH.Syntax (lift, liftData)
 
 import           RON.Data (Replicated (..), ReplicatedAsObject (..),
@@ -34,13 +38,21 @@
 import           RON.Data.VersionVector (VersionVector)
 import           RON.Event (ReplicaClock)
 import           RON.Schema as X
+import qualified RON.Schema.EDN as EDN
 import           RON.Types (Object (..), UUID)
 import qualified RON.UUID as UUID
 
+mkReplicated :: QuasiQuoter
+mkReplicated = QuasiQuoter{quoteDec, quoteExp = e, quotePat = e, quoteType = e}
+  where
+    e = error "declaration only"
+    quoteDec = EDN.parseSchema >=> mkReplicated'
+
 -- | Generate Haskell types from RON-Schema
-mkReplicated :: Schema -> TH.DecsQ
-mkReplicated = fmap fold . traverse fromDecl where
+mkReplicated' :: Schema -> TH.DecsQ
+mkReplicated' = fmap fold . traverse fromDecl where
     fromDecl decl = case decl of
+        DOpaque    _ -> pure []
         DStructLww s -> mkReplicatedStructLww s
 
 -- | Type-directing newtype
@@ -99,7 +111,7 @@
 
     StructLww{structName, structFields, structAnnotations} = struct
 
-    StructAnnotations{saHaskellDeriving, saHaskellFieldPrefix} =
+    StructAnnotations{saHaskellFieldPrefix, saHaskellFieldCaseTransform} =
         structAnnotations
 
     name = mkNameT structName
@@ -116,8 +128,7 @@
                 Map.assocs structFields
             , let viewType = mkViewType fieldType
             ]]
-        [TH.derivClause Nothing . map (conT . mkNameT) $
-            toList saHaskellDeriving]
+        []
 
     mkInstanceReplicated = [d|
         instance Replicated $structT where
@@ -167,7 +178,12 @@
             ]
         consP = conP name [varP field'Var | Field'{field'Var} <- fields]
 
-    mkHaskellFieldName = (saHaskellFieldPrefix <>)
+    mkHaskellFieldName base = saHaskellFieldPrefix <> base' where
+        base' = case saHaskellFieldCaseTransform of
+            Nothing        -> base
+            Just TitleCase -> case Text.uncons base of
+                Nothing            -> base
+                Just (b, baseTail) -> Text.cons (toTitle b) baseTail
 
     mkAccessors field' = do
         a <- varT <$> TH.newName "a"
@@ -228,11 +244,9 @@
         TRga       item                  -> wrapList item
         TStructLww StructLww{structName} -> conT $ mkNameT structName
         TVersionVector                   -> [t| VersionVector |]
-    TOpaque Opaque{opaqueAnnotations} -> let
+    TOpaque Opaque{opaqueName, opaqueAnnotations} -> let
         OpaqueAnnotations{oaHaskellType} = opaqueAnnotations
-        in case oaHaskellType of
-            Just name -> conT $ mkNameT name
-            Nothing   -> fail "Opaque type must define a Haskell type"
+        in conT $ mkNameT $ fromMaybe opaqueName oaHaskellType
   where
     wrapList a = [t| [$(mkViewType a)] |]
 
diff --git a/lib/RON/Storage.hs b/lib/RON/Storage.hs
--- a/lib/RON/Storage.hs
+++ b/lib/RON/Storage.hs
@@ -15,7 +15,6 @@
     , DocVersion
     , MonadStorage (..)
     , createDocument
-    , createVersion
     , decodeDocId
     , loadDocument
     , modify
diff --git a/lib/RON/Text/Parse.hs b/lib/RON/Text/Parse.hs
--- a/lib/RON/Text/Parse.hs
+++ b/lib/RON/Text/Parse.hs
@@ -37,14 +37,14 @@
 import           Data.Text (Text)
 
 import qualified RON.Base64 as Base64
-import           RON.Internal.Word (Word2, Word4, Word60, b00, b0000, b01, b10,
-                                    b11, ls60, safeCast)
 import           RON.Types (Atom (AFloat, AInteger, AString, AUuid),
                             Object (..), Op (..),
                             OpTerm (THeader, TQuery, TRaw, TReduced),
                             RawOp (..), StateChunk (..), StateFrame,
                             UUID (UUID), WireChunk (Query, Raw, Value),
                             WireFrame, WireReducedChunk (..))
+import           RON.Util.Word (Word2, Word4, Word60, b00, b0000, b01, b10, b11,
+                                ls60, safeCast)
 import           RON.UUID (UuidFields (..))
 import qualified RON.UUID as UUID
 
diff --git a/lib/RON/Text/Serialize.hs b/lib/RON/Text/Serialize.hs
--- a/lib/RON/Text/Serialize.hs
+++ b/lib/RON/Text/Serialize.hs
@@ -23,7 +23,6 @@
 import qualified Data.Aeson as Json
 import qualified Data.ByteString.Lazy as BSL
 import qualified Data.ByteString.Lazy.Char8 as BSLC
-import qualified Data.ByteString.Lazy.Search as BSL
 import qualified Data.Map.Strict as Map
 import           Data.Text (Text)
 import           Data.Traversable (for)
@@ -135,9 +134,17 @@
 -- | Serialize a string atom
 serializeString :: Text -> ByteStringL
 serializeString =
-    fixQuotes . BSL.replace "'" ("\\'" :: ByteString) . Json.encode
+    wrapSingleQuotes . escapeApostrophe . stripDoubleQuotes . Json.encode
   where
-    fixQuotes = (`BSLC.snoc` '\'') . BSLC.cons '\'' . BSL.init . BSL.tail
+    wrapSingleQuotes = (`BSLC.snoc` '\'') . BSLC.cons '\''
+    stripDoubleQuotes = BSL.init . BSL.tail
+    escapeApostrophe s = let
+        (s1, s2) = BSLC.break (== '\'') s
+        in
+        if BSL.null s2 then
+            s1
+        else
+            s1 <> "\\'" <> escapeApostrophe (BSL.tail s2)
 
 -- | Serialize a payload in stream context
 serializePayload
diff --git a/lib/RON/Text/Serialize/UUID.hs b/lib/RON/Text/Serialize/UUID.hs
--- a/lib/RON/Text/Serialize/UUID.hs
+++ b/lib/RON/Text/Serialize/UUID.hs
@@ -21,9 +21,9 @@
 import           Data.Ord (comparing)
 
 import qualified RON.Base64 as Base64
-import           RON.Internal.Word (pattern B00, pattern B0000, pattern B01,
-                                    pattern B10, pattern B11, Word2, Word60,
-                                    ls60, safeCast)
+import           RON.Util.Word (pattern B00, pattern B0000, pattern B01,
+                                pattern B10, pattern B11, Word2, Word60, ls60,
+                                safeCast)
 import           RON.UUID (UUID (..), UuidFields (..), split, zero)
 
 -- | Serialize UUID without context (used for test)
diff --git a/lib/RON/Types.hs b/lib/RON/Types.hs
--- a/lib/RON/Types.hs
+++ b/lib/RON/Types.hs
@@ -24,7 +24,6 @@
 
 import           RON.Internal.Prelude
 
-import           Control.DeepSeq (NFData)
 import           Data.Data (Data)
 import           Data.Text (Text)
 import           GHC.Generics (Generic)
@@ -33,7 +32,7 @@
 
 -- | Atom — a payload element
 data Atom = AFloat Double | AInteger Int64 | AString Text | AUuid UUID
-    deriving (Data, Eq, Generic, Hashable, NFData, Show)
+    deriving (Data, Eq, Generic, Hashable, Show)
 
 -- | Raw op
 data RawOp = RawOp
@@ -44,7 +43,7 @@
     , op       :: Op
         -- ^ other keys and payload, that are common with reduced op
     }
-    deriving (Data, Eq, Generic, NFData)
+    deriving (Data, Eq, Generic)
 
 -- | “Reduced” op (op from reduced chunk)
 data Op = Op
@@ -55,7 +54,7 @@
     , opPayload :: [Atom]
         -- ^ payload
     }
-    deriving (Data, Eq, Generic, Hashable, NFData, Show)
+    deriving (Data, Eq, Generic, Hashable, Show)
 
 instance Show RawOp where
     show RawOp{opType, opObject, op = Op{opEvent, opRef, opPayload}} =
@@ -77,11 +76,11 @@
     { wrcHeader :: RawOp
     , wrcBody   :: [Op]
     }
-    deriving (Data, Eq, Generic, NFData, Show)
+    deriving (Data, Eq, Generic, Show)
 
 -- | Common chunk
 data WireChunk = Raw RawOp | Value WireReducedChunk | Query WireReducedChunk
-    deriving (Data, Eq, Generic, NFData, Show)
+    deriving (Data, Eq, Generic, Show)
 
 -- | Common frame
 type WireFrame = [WireChunk]
diff --git a/lib/RON/UUID.hs b/lib/RON/UUID.hs
--- a/lib/RON/UUID.hs
+++ b/lib/RON/UUID.hs
@@ -29,7 +29,6 @@
 
 import           RON.Internal.Prelude
 
-import           Control.DeepSeq (NFData)
 import           Data.Bits (shiftL, shiftR, (.|.))
 import qualified Data.ByteString.Char8 as BSC
 import           Data.Char (chr, toUpper)
@@ -38,17 +37,16 @@
 import           GHC.Generics (Generic)
 
 import qualified RON.Base64 as Base64
-import           RON.Internal.Word (pattern B00, pattern B0000, pattern B01,
-                                    pattern B10, pattern B11, Word2, Word4,
-                                    Word60, leastSignificant2,
-                                    leastSignificant4, leastSignificant60,
-                                    safeCast)
+import           RON.Util.Word (pattern B00, pattern B0000, pattern B01,
+                                pattern B10, pattern B11, Word2, Word4, Word60,
+                                leastSignificant2, leastSignificant4,
+                                leastSignificant60, safeCast)
 
 -- | Universally unique identifier of anything
 data UUID = UUID
     {-# UNPACK #-} !Word64
     {-# UNPACK #-} !Word64
-    deriving (Data, Eq, Generic, Hashable, NFData, Ord)
+    deriving (Data, Eq, Generic, Hashable, Ord)
 
 -- | RON-Text-encoding
 instance Show UUID where
diff --git a/lib/RON/Util/Word.hs b/lib/RON/Util/Word.hs
new file mode 100644
--- /dev/null
+++ b/lib/RON/Util/Word.hs
@@ -0,0 +1,193 @@
+{-# LANGUAGE BinaryLiterals #-}
+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE PatternSynonyms #-}
+{-# LANGUAGE TypeApplications #-}
+
+module RON.Util.Word
+    (
+    -- * Word2
+      Word2
+    , b00, b01, b10, b11
+    , pattern B00, pattern B01, pattern B10, pattern B11
+    , leastSignificant2
+    -- * Word4
+    , Word4
+    , b0000, b0001, b0010, b0011, b0100, b0101, b0110, b0111
+    , b1000, b1001, b1010, b1011, b1100, b1101, b1110, b1111
+    , pattern B0000
+    , leastSignificant4
+    -- * Word6
+    , Word6 (..)
+    , leastSignificant6
+    , ls6
+    -- * Word8
+    , Word8
+    -- * Word12
+    , Word12
+    , leastSignificant12
+    , ls12
+    -- * Word16
+    , Word16
+    -- * Word24
+    , Word24
+    , leastSignificant24
+    , ls24
+    -- * Word32
+    , Word32
+    -- * Word60
+    , Word60
+    , leastSignificant60
+    , ls60
+    , toWord60
+    , word60add
+    -- * Word64
+    , Word64
+    -- * SafeCast
+    , SafeCast (..)
+    ) where
+
+import           Data.Bits ((.&.))
+import           Data.Coerce (coerce)
+import           Data.Fixed (Fixed, HasResolution)
+import           Data.Hashable (Hashable, hashUsing, hashWithSalt)
+import           Data.Word (Word16, Word32, Word64, Word8)
+
+newtype Word2 = W2 Word8
+    deriving (Eq, Ord, Show)
+
+b00, b01, b10, b11 :: Word2
+b00 = W2 0b00
+b01 = W2 0b01
+b10 = W2 0b10
+b11 = W2 0b11
+
+pattern B00 :: Word2
+pattern B00 = W2 0b00
+pattern B01 :: Word2
+pattern B01 = W2 0b01
+pattern B10 :: Word2
+pattern B10 = W2 0b10
+pattern B11 :: Word2
+pattern B11 = W2 0b11
+{-# COMPLETE B00, B01, B10, B11 #-}
+
+-- | 'Word2' smart constructor dropping upper bits
+leastSignificant2 :: Integral integral => integral -> Word2
+leastSignificant2 = W2 . (0b11 .&.) . fromIntegral
+
+newtype Word4 = W4 Word8
+    deriving (Eq, Ord, Show)
+
+b0000, b0001, b0010, b0011, b0100, b0101, b0110, b0111 :: Word4
+b1000, b1001, b1010, b1011, b1100, b1101, b1110, b1111 :: Word4
+b0000 = W4 0b0000
+b0001 = W4 0b0001
+b0010 = W4 0b0010
+b0011 = W4 0b0011
+b0100 = W4 0b0100
+b0101 = W4 0b0101
+b0110 = W4 0b0110
+b0111 = W4 0b0111
+b1000 = W4 0b1000
+b1001 = W4 0b1001
+b1010 = W4 0b1010
+b1011 = W4 0b1011
+b1100 = W4 0b1100
+b1101 = W4 0b1101
+b1110 = W4 0b1110
+b1111 = W4 0b1111
+
+pattern B0000 :: Word4
+pattern B0000 = W4 0b0000
+
+-- | 'Word4' smart constructor dropping upper bits
+leastSignificant4 :: Integral integral => integral -> Word4
+leastSignificant4 = W4 . (0xF .&.) . fromIntegral
+
+newtype Word6 = W6 Word8
+    deriving (Eq, Ord, Show)
+
+-- | 'Word6' smart constructor dropping upper bits
+leastSignificant6 :: Integral integral => integral -> Word6
+leastSignificant6 = W6 . (0x3F .&.) . fromIntegral
+
+-- | 'leastSignificant6' specialized for 'Word8'
+ls6 :: Word8 -> Word6
+ls6 = W6 . (0x3F .&.)
+
+newtype Word12 = W12 Word16
+    deriving (Eq, Ord, Show)
+
+-- | 'Word12' smart constructor dropping upper bits
+leastSignificant12 :: Integral integral => integral -> Word12
+leastSignificant12 = W12 . (0xFFF .&.) . fromIntegral
+
+-- | 'leastSignificant12' specialized for 'Word16'
+ls12 :: Word16 -> Word12
+ls12 = W12 . (0xFFF .&.)
+
+newtype Word24 = W24 Word32
+    deriving (Eq, Ord, Show)
+
+-- | 'Word24' smart constructor dropping upper bits
+leastSignificant24 :: Integral integral => integral -> Word24
+leastSignificant24 = W24 . (0xFFFFFF .&.) . fromIntegral
+
+-- | 'leastSignificant24' specialized for 'Word32'
+ls24 :: Word32 -> Word24
+ls24 = W24 . (0xFFF .&.)
+
+newtype Word60 = W60 Word64
+    deriving (Enum, Eq, Ord, Show)
+
+instance Bounded Word60 where
+    minBound = W60 0
+    maxBound = W60 0xFFFFFFFFFFFFFFF
+
+instance Hashable Word60 where
+    hashWithSalt = hashUsing @Word64 coerce
+
+-- | 'Word60' smart constructor dropping upper bits
+leastSignificant60 :: Integral integral => integral -> Word60
+leastSignificant60 = W60 . (0xFFFFFFFFFFFFFFF .&.) . fromIntegral
+
+-- | 'leastSignificant60' specialized for 'Word64'
+ls60 :: Word64 -> Word60
+ls60 = W60 . (0xFFFFFFFFFFFFFFF .&.)
+
+-- | 'Word60' smart constructor checking domain
+toWord60 :: Word64 -> Maybe Word60
+toWord60 w
+    | w < 0x1000000000000000 = Just $ W60 w
+    | otherwise              = Nothing
+
+word60add :: Word60 -> Word60 -> Word60
+word60add (W60 a) (W60 b) = leastSignificant60 $ a + b
+
+class SafeCast v w where
+    safeCast :: v -> w
+
+instance SafeCast Word2  Int     where safeCast = fromIntegral @Word8 . coerce
+instance SafeCast Word2  Word4   where safeCast = coerce
+instance SafeCast Word2  Word8   where safeCast = coerce
+instance SafeCast Word2  Word64  where safeCast = fromIntegral @Word8 . coerce
+instance SafeCast Word4  Int     where safeCast = fromIntegral @Word8 . coerce
+instance SafeCast Word4  Word64  where safeCast = fromIntegral @Word8 . coerce
+instance SafeCast Word4  Word8   where safeCast = coerce
+instance SafeCast Word6  Int     where safeCast = fromIntegral @Word8 . coerce
+instance SafeCast Word6  Word8   where safeCast = coerce
+instance SafeCast Word6  Word60  where safeCast = coerce @Word64
+                                                . fromIntegral @Word8 . coerce
+instance SafeCast Word6  Word64  where safeCast = fromIntegral @Word8 . coerce
+instance SafeCast Word8  Word32  where safeCast = fromIntegral
+instance SafeCast Word8  Word64  where safeCast = fromIntegral
+instance SafeCast Word12 Word64  where safeCast = fromIntegral @Word16 . coerce
+instance SafeCast Word24 Word64  where safeCast = fromIntegral @Word32 . coerce
+instance SafeCast Word24 Word32  where safeCast = coerce
+instance SafeCast Word60 Word64  where safeCast = coerce
+instance SafeCast Word64 Integer where safeCast = fromIntegral
+
+-- Fixed are Integers inside, so have arbitrary magnitude
+instance HasResolution e => SafeCast Word64 (Fixed e) where
+    safeCast = fromIntegral
diff --git a/ron.cabal b/ron.cabal
--- a/ron.cabal
+++ b/ron.cabal
@@ -1,7 +1,7 @@
 cabal-version:  2.2
 
 name:           ron
-version:        0.2
+version:        0.3
 
 bug-reports:    https://github.com/ff-notes/ron/issues
 category:       Distributed Systems, Protocol, Database
@@ -56,20 +56,20 @@
         bytestring,
         containers,
         data-default,
-        deepseq,
         Diff,
         directory,
         errors,
         extra,
         filepath,
         hashable,
+        hedn,
         mtl,
         network-info,
         safe,
-        stringsearch,
         template-haskell,
         text,
         time,
+        transformers,
         unordered-containers,
         vector,
     exposed-modules:
@@ -79,7 +79,6 @@
         RON.Binary.Serialize
         RON.Binary.Types
         RON.Data
-        RON.Data.Internal
         RON.Data.LWW
         RON.Data.ORSet
         RON.Data.RGA
@@ -88,8 +87,6 @@
         RON.Epoch
         RON.Event
         RON.Event.Simulation
-        RON.Internal.Prelude
-        RON.Internal.Word
         RON.Schema
         RON.Schema.TH
         RON.Storage
@@ -100,10 +97,14 @@
         RON.Text.Serialize
         RON.Text.Serialize.UUID
         RON.Types
+        RON.Util.Word
         RON.UUID
     other-modules:
         Attoparsec.Extra
         Data.ZigZag
+        RON.Data.Internal
+        RON.Internal.Prelude
+        RON.Schema.EDN
     hs-source-dirs: lib
 
 benchmark bench
