packages feed

packstream-bolt-0.1.0.0: readme.md

# packstream

Haskell implementation of the [PackStream](https://neo4j.com/docs/bolt/current/packstream/) binary serialization format, used as the wire format by Neo4j's BOLT protocol.

PackStream is similar to [MessagePack](https://msgpack.org/) but adds a `Structure` type for encoding typed graph objects (nodes, relationships, temporal values, spatial points, etc.).

> **Note:** Most users should depend on [bolty](https://github.com/philippedev101/bolty) instead, which provides a full Neo4j driver. This package is useful if you need to work with the PackStream wire format directly.

## Types

PackStream defines 9 value types, represented by the `Ps` algebraic data type:

| PackStream type | Haskell constructor | Description |
|---|---|---|
| Null | `PsNull` | Missing or empty value |
| Boolean | `PsBoolean !Bool` | True or false |
| Integer | `PsInteger !PSInteger` | Signed integer (up to 64-bit) |
| Float | `PsFloat !Double` | 64-bit IEEE 754 float |
| Bytes | `PsBytes !ByteString` | Raw byte array |
| String | `PsString !Text` | UTF-8 text |
| List | `PsList !(Vector Ps)` | Ordered collection |
| Dictionary | `PsDictionary !(HashMap Text Ps)` | Key-value map (text keys) |
| Structure | `PsStructure !Tag !(Vector Ps)` | Tagged composite (tag byte + positional fields) |

Integers use a variable-width encoding: values in `[-16, 127]` are encoded in a single byte (no tag), with `INT_8`, `INT_16`, `INT_32`, and `INT_64` for larger values.

## The `PackStream` type class

Convert between Haskell types and `Ps` values:

```haskell
class PackStream a where
  toPs   :: a -> Ps           -- encode to Ps AST
  toBinary :: a -> Put        -- encode directly to wire format (optional, defaults to putPs . toPs)
  fromPs :: Ps -> Result a    -- decode from Ps AST
```

Built-in instances exist for `Bool`, `Int`, `Int64`, `Word8`, `Word16`, `Word32`, `Double`, `Text`, `ByteString`, `Vector`, `HashMap Text`, `Maybe`, tuples (up to 9), and more.

## Quick start

### Encoding and decoding `Ps` values

```haskell
import Data.PackStream (pack, unpack)
import Data.PackStream.Ps (Ps(..))
import qualified Data.HashMap.Lazy as H

-- Encode a dictionary to binary
let ps = PsDictionary $ H.fromList
      [ ("name", PsString "Alice")
      , ("age",  PsInteger 30)
      ]
let bytes = pack ps  -- :: Lazy ByteString

-- Decode binary back to a Ps value
case unpack bytes of
  Right val -> print (val :: Ps)
  Left err  -> putStrLn $ "Decode error: " <> show err
```

### Custom types

```haskell
import Data.PackStream.Ps (PackStream(..), Ps(..), (.:), withDictionary)
import Data.PackStream.Result (Result(..))
import qualified Data.HashMap.Lazy as H

data Person = Person { name :: Text, age :: Int64 }

instance PackStream Person where
  toPs Person{name, age} = PsDictionary $ H.fromList
    [ ("name", toPs name)
    , ("age",  toPs age)
    ]

  fromPs = withDictionary "Person" $ \m -> do
    name <- m .: "name"  -- uses (.:) operator for key lookup + decode
    age  <- m .: "age"
    pure $ Person name age
```

### Structures

Structures are the key differentiator from MessagePack. They carry a tag byte identifying the type and positional fields:

```haskell
import Data.PackStream.Ps (Ps(..), PackStream(..))
import qualified Data.Vector as V

-- A Date structure (tag 0x44) with one field: days since Unix epoch
let date = PsStructure 0x44 (V.singleton (PsInteger 19737))

-- Define a custom structure type
data MyDate = MyDate { days :: Int64 }

instance PackStream MyDate where
  toPs (MyDate d) = PsStructure 0x44 (V.singleton (toPs d))
  fromPs (PsStructure 0x44 fs) | V.length fs == 1 = MyDate <$> fromPs (fs V.! 0)
  fromPs other = typeMismatch "MyDate" other
```

Neo4j uses structures for graph types (Node `0x4E`, Relationship `0x52`, Path `0x50`), temporal types (Date `0x44`, Time `0x54`, DateTime `0x49`, Duration `0x45`), and spatial types (Point2D `0x58`, Point3D `0x59`).

## Module structure

**Public API:**

- `Data.PackStream` — top-level `pack`/`unpack` + re-exports
- `Data.PackStream.Ps` — core `Ps` type, `PackStream` class, operators
- `Data.PackStream.Result` — `Result` type (`Success`/`Error`)
- `Data.PackStream.Tags` — wire format tag constants
- `Data.PackStream.Timestamp` — helpers for epoch-based temporal conversions

**Internal modules** (exposed but not part of the stable API):

- `Data.PackStream.Get` / `Data.PackStream.Get.Internal` — binary decoding primitives
- `Data.PackStream.Put` — binary encoding primitives
- `Data.PackStream.Integer` — variable-width integer encoding
- `Data.PackStream.Generic` — GHC.Generics-based deriving (experimental)
- `Data.PackStream.Assoc` — ordered association lists
- `Compat.Binary` / `Compat.Prelude` — compatibility wrappers

## Supported GHC versions

9.6.7, 9.8.4, 9.10.3, 9.12.3

## License

Apache-2.0