# Migrating to endpoint-libs 3.0
Version 3 changes the in-memory ownership of `WireMessage` payloads. The WebSocket,
MCP, and length-delimited wire formats are byte-identical to version 2; deployed peers
do not need a coordinated protocol rollout.
## Why this is a major version
Version 2 represented text with `String` and data with `Vec<u8>`. Both the framed
transport and tungstenite already owned byte buffers, so receiving a message copied the
complete payload into a second allocation. Version 3 retains those buffers:
| `WireMessage::Text(String)` | `WireMessage::Text(Utf8Bytes)` |
| `WireMessage::Binary(Vec<u8>)` | `WireMessage::Binary(bytes::Bytes)` |
| `WireMessage::Ping(Vec<u8>)` | `WireMessage::Ping(bytes::Bytes)` |
| `WireMessage::Pong(Vec<u8>)` | `WireMessage::Pong(bytes::Bytes)` |
| `CloseFrame.reason: String` | `CloseFrame.reason: Utf8Bytes` |
Code that only calls `as_text()`, `as_bytes()`, or `is_close()` does not change. Direct
construction and destructuring are intentionally compile-time breaking changes.
Version 3 also removes the deprecated `database` feature and `libs::database` module.
They had no consumers in the maintained workspace and bundled an untested PostgreSQL
pooling layer into the transport crate's release and security surface. Applications that
still need those helpers should own their database layer directly with
`tokio-postgres`/`deadpool-postgres` rather than depending on endpoint-libs for it.
## Construction
Prefer the constructors when the concrete payload type does not matter:
```rust
use endpoint_libs::libs::ws::WireMessage;
let text = WireMessage::text(String::from("hello"));
let binary = WireMessage::binary(vec![1, 2, 3]);
let ping = WireMessage::ping(vec![4, 5, 6]);
```
Existing variant construction needs an explicit conversion:
```rust
let text = WireMessage::Text(String::from("hello").into());
let binary = WireMessage::Binary(vec![1, 2, 3].into());
```
Converting an owned `String` or `Vec<u8>` into the new payload transfers its allocation.
Converting a borrowed slice copies because the caller retains ownership.
## Reading and retaining payloads
`Utf8Bytes` dereferences to `str`, so parsers and formatting continue to borrow it:
```rust
if let WireMessage::Text(text) = message {
let value: serde_json::Value = serde_json::from_str(&text)?;
}
```
Use `WireMessage::as_bytes()` to borrow any application payload or `into_data()` to take
its immutable `Bytes` allocation. Cloning `Utf8Bytes` or `Bytes` increments a reference
count rather than copying the payload.
## Release coordination
This is an API migration, not a wire-protocol migration. Publish and update the dependency
chain in the order documented in [`release-order.md`](release-order.md): endpoint-libs,
then honey_id-types and endpoint-gen, then each backend with endpoint-libs and
honey_id-types bumped together.