Skip to content

Releases: TrustBound/dream

2.4.0

11 Mar 04:19
Immutable release. Only release title and notes can be modified.
924263c

Choose a tag to compare

Dream 2.4.0 Release Notes

Release Date: March 10, 2026

Dream 2.4.0 introduces native Server-Sent Events (SSE) support backed by dedicated OTP actors, fixing a critical bug where SSE streams would stall after a few events. The new dream/servers/mist/sse module follows the same stash-and-upgrade pattern as WebSockets, giving each SSE connection its own mailbox and eliminating TCP message contention.

Key Highlights

  • Native SSE supportupgrade_to_sse spawns a dedicated OTP actor per connection, replacing the broken chunked encoding approach
  • Builder-style event APIevent, event_name, event_id, event_retry for structured SSE events
  • No closures — explicit dependencies parameter, consistent with Dream's WebSocket pattern
  • Deprecation of sse_response — old function kept for compatibility but marked deprecated with migration guidance
  • Comprehensive testing — unit tests, tested documentation snippets, and Cucumber integration tests
  • Full documentation — new SSE guide, updated streaming API reference, example application

What's New

Native SSE Module: dream/servers/mist/sse

The new module provides Dream's SSE abstraction for the Mist server adapter. It upgrades an HTTP request to an SSE connection backed by a dedicated OTP actor, avoiding the stalling issues of chunked transfer encoding.

import dream/servers/mist/sse

pub fn handle_events(request, _context, _services) {
  sse.upgrade_to_sse(
    request,
    dependencies: MyDeps,
    on_init: handle_init,
    on_message: handle_message,
  )
}

New Types:

  • sse.SSEConnection — opaque handle to the SSE connection
  • sse.Event — structured SSE event built with builder functions
  • sse.Action(state, message) — controls the SSE actor lifecycle

Event Builders:

sse.event("{\"count\": 42}")
|> sse.event_name("tick")
|> sse.event_id("42")
|> sse.event_retry(5000)

Action Helpers:

  • sse.continue_connection(state) — keep the actor running
  • sse.continue_connection_with_selector(state, selector) — keep running with a new selector
  • sse.stop_connection() — shut down the actor

SSE Example Application

Added examples/sse/ with a complete ticker application:

  • Real-time counter events streamed to connected clients
  • Named events with IDs for client-side filtering and reconnection
  • Cucumber/Gherkin integration tests using HTTPoison's async streaming
  • Scenario verifying events stream continuously without stalling

SSE Documentation

  • New docs/guides/sse.md covering SSE vs WebSockets vs streaming, the full upgrade lifecycle, event builders, broadcasting with Subject and broadcaster, client-side EventSource, and testing
  • Updated docs/reference/streaming-api.md with upgrade_to_sse API reference, event builder signatures, and action helper documentation
  • Updated docs/guides/streaming.md to direct users to the new dedicated SSE guide

Bug Fix

SSE Streams Stalling After 2–3 Events

Bug: response.sse_response used Stream(yielder) which Mist converted to chunked transfer encoding. The yielder blocked in the mist handler process's mailbox, competing with TCP messages (ACKs, window updates). After 2–3 events the mailbox would back up and the stream would stall.

Fix: The new sse.upgrade_to_sse function calls Mist's native server_sent_events which spawns a dedicated OTP actor with its own mailbox. SSE events and TCP messages no longer contend for the same process, eliminating the stalling entirely.

Deprecated

response.sse_response

The sse_response function is deprecated. It remains available for backward compatibility but its doc comment now includes a deprecation warning directing users to dream/servers/mist/sse.upgrade_to_sse.

Migration: Replace sse_response with upgrade_to_sse:

// Before (stalls after a few events)
import dream/http/response

pub fn handle_events(request, _context, _services) {
  response.sse_response(200, my_yielder, "text/event-stream")
}

// After (dedicated actor, no stalling)
import dream/servers/mist/sse

pub fn handle_events(request, _context, _services) {
  sse.upgrade_to_sse(
    request,
    dependencies: MyDeps,
    on_init: handle_init,
    on_message: handle_message,
  )
}

Testing

  • Unit tests for event builders and action wrappers in test/dream/servers/mist/sse_test.gleam
  • Tested documentation snippets in test/snippets/ ensuring all code examples from docs compile and run
  • Cucumber integration tests in examples/sse/ verifying:
    • Correct SSE response headers (Content-Type: text/event-stream)
    • Events stream with expected data payloads
    • Named events include event: and id: fields
    • Events do not stall (the original bug scenario)

Upgrading

Update your dependencies:

[dependencies]
dream = ">= 2.4.0 and < 3.0.0"

Then run:

gleam deps download

Migration Guide

No breaking changes! This release is fully backward compatible.

If you are currently using sse_response, we strongly recommend migrating to the new SSE module:

  1. Import the SSE module:

    import dream/servers/mist/sse
  2. Define your message type and dependencies:

    pub type MyMessage { Tick }
    pub type MyDeps { MyDeps }
  3. Replace sse_response with upgrade_to_sse:

    pub fn handle_events(request, _context, _services) {
      sse.upgrade_to_sse(
        request,
        dependencies: MyDeps,
        on_init: handle_init,
        on_message: handle_message,
      )
    }
  4. See examples/sse/ and docs/guides/sse.md for a complete walkthrough.

Documentation

Community


Full Changelog: CHANGELOG.md

2.3.3

07 Mar 10:41
Immutable release. Only release title and notes can be modified.
728187c

Choose a tag to compare

Dream 2.3.3 Release Notes

Release Date: March 7, 2026

This release fixes a bug where multiple Set-Cookie headers were collapsed into a single header, causing browsers to only receive the last cookie.

Key Highlights

  • Set-Cookie fix: Multiple cookies now each produce their own Set-Cookie header per RFC 6265
  • Comprehensive test coverage: 7 new tests validate multi-cookie behavior and RFC compliance

Fixed

The mist response converter used set_header (which replaces existing headers with the same name) for every header, including Set-Cookie. RFC 6265 requires each cookie to be sent as a separate Set-Cookie header — browsers do not parse comma-separated Set-Cookie values.

add_header now uses prepend_header (which allows duplicates) for set-cookie headers, and set_header (which replaces) for everything else. This matches the Gleam standard library's own set_cookie convention.

Before

fn add_header(acc, header) {
  http_response.set_header(acc, header.0, header.1)
}

After

fn add_header(acc, header) {
  case header.0 {
    "set-cookie" -> http_response.prepend_header(acc, header.0, header.1)
    _ -> http_response.set_header(acc, header.0, header.1)
  }
}

Added

  • count_mist_headers test matcher for verifying header counts by name
  • extract_all_mist_header_values test matcher for extracting all values of a header
  • 7 new tests covering:
    • Multiple cookies produce separate Set-Cookie headers
    • Each cookie value is individually present
    • Three cookies produce three headers
    • Manual Set-Cookie in headers coexists with cookies from the cookies field
    • Duplicate non-cookie headers are still deduplicated
    • Cookies with attributes each get their own header
    • Cookies alongside other headers don't interfere

Upgrading

Update your dependencies:

[dependencies]
dream = ">= 2.3.3 and < 3.0.0"

Then run:

gleam deps download

Documentation


2.3.2

05 Feb 03:49
Immutable release. Only release title and notes can be modified.
881daef

Choose a tag to compare

Dream 2.3.2 Release Notes

Release Date: February 4, 2026

This release focuses on startup clarity and smoother local testing.

Key Highlights

  • Fail-fast startup: Servers now report a clear error when a port is already in use
  • Local test parity with CI: One Makefile target prepares the integration databases
  • Cleaner server startup flow: Internal helpers keep the public API simple

Thanks

Thanks to daniellionel01 for reporting the issue.

Fixed

Dream now detects port conflicts before starting the Mist server and returns a
direct, actionable error message. No more “it started” when it didn’t.

Added

A new setup-integration-dbs target prepares the example databases and
applies migrations, matching CI’s database setup steps.

Upgrading

Update your dependencies:

[dependencies]
dream = ">= 2.3.2 and < 3.0.0"

Then run:

gleam deps download

Documentation


2.3.1

13 Dec 22:49
Immutable release. Only release title and notes can be modified.
32c8113

Choose a tag to compare

Dream 2.3.1 Release Notes

Release Date: December 13, 2025

This is not “just a patch release”.

This is the moment web development looks back on and quietly says: “…yeah, that changed everything.”

Historians will argue where the modern era began.
Some will cite the printing press.
Some will cite the internet.

They will be wrong.

The modern era began when we stopped writing 418 like we were chiseling responses into stone tablets, and started writing the only thing that was ever true:
teapot.

Key Highlights

  • A new era of human-readable computing: teapot exists, and your codebase can finally stop pretending it enjoys numerals
  • RFC 2324 alignment: Dream now stands on the shoulders of giants (and the shoulders are holding coffee)
  • Massively improved signal-to-number ratio: one less “magic number”, one more semantic truth
  • Societal impact: code review comments reduced by up to 418% (this statistic is obviously real because it contains a number)

The Feature That Will Be Remembered

We added the single most important public constant in the history of constants:

teapot = 418

Yes, it’s “just” an Int.
So is gravity.

Before (the dark ages)

text_response(418, "No.")

After (enlightenment)

import dream/http/response.{text_response}
import dream/http/status.{teapot}

pub fn brew_coffee(_request, _context, _services) {
  text_response(teapot, "Server refuses to brew coffee because it is, in fact, a teapot.")
}

Your future self will weep with gratitude.
Your teammates will weep with gratitude.
Somewhere, an editor linter will briefly consider smiling.

Compatibility

This is fully backwards-compatible.
You can keep using 418 if you absolutely insist on living dangerously.

Upgrading

Update your dependencies:

[dependencies]
dream = ">= 2.3.1 and < 3.0.0"

Then run:

gleam deps download

Documentation


Full Changelog: CHANGELOG.md

2.3.0

01 Dec 15:23
Immutable release. Only release title and notes can be modified.
f044fbf

Choose a tag to compare

Dream 2.3.0 Release Notes

Release Date: December 1, 2025

Dream 2.3.0 upgrades to dream_test for a better testing experience, and fixes two router bugs that were discovered during the migration.

Key Highlights

  • 🧪 Test Framework Upgrade - Switched to dream_test for BDD-style testing with process isolation
  • 🐛 Router Parameter Fix - Parameters now returned in correct path order
  • 🔧 Extension Pattern Fix - Patterns like /products.{json,xml} now work correctly
  • 📚 Testing Documentation - Complete rewrite for dream_test framework

Why We Switched to dream_test

dream_test provides features that gleeunit doesn't offer:

Feature What You Get
BDD-style syntax describe/it blocks that read like documentation
Process isolation Each test runs in its own BEAM process
Lifecycle hooks before_each, after_each, before_all, after_all
Chainable matchers should() |> be_ok() |> equal(expected)
Parallel execution Tests run concurrently across cores
Crash isolation One test crashing doesn't affect others
Timeout protection Hanging tests get killed automatically

What's Changed

Test Framework Migration

Replaced gleeunit with dream_test across all 21 test files:

Before (gleeunit):

pub fn my_test_test() {
  my_function()
  |> should.equal(expected)
}

After (dream_test):

pub fn tests() -> UnitTest {
  describe("my_module", [
    describe("my_function", [
      it("does X when Y", fn() {
        // Arrange
        let input = create_input()

        // Act
        let result = my_function(input)

        // Assert
        result
        |> should()
        |> equal(expected)
        |> or_fail_with("Expected X")
      }),
    ]),
  ])
}

New Infrastructure:

  • 21 custom matchers in test/matchers/ for cleaner assertions
  • Reusable fixtures in test/fixtures/ (request, response, handler, hooks)
  • Process isolation for all 239 tests
  • Self-identifying benchmark output for parallel execution

Router Bug Fixes

Parameter Ordering Fixed:

Parameters are now returned in the order they appear in the path:

// Route: /users/:user_id/posts/:post_id
// Request: /users/1/posts/2

// Before (2.2.0): [("post_id", "2"), ("user_id", "1")]  <- WRONG!
// After (2.3.0):  [("user_id", "1"), ("post_id", "2")]  <- Correct

Extension Pattern Matching Fixed:

Added LiteralExtension segment type for patterns like /products.{json,xml}:

// Route definition
route(Get, "/products.{json,xml}", products_controller, [])

// Before (2.2.0): /products.json -> No match
// After (2.3.0):  /products.json -> Match!

Documentation Updates

  • Rewrote docs/guides/testing.md - Complete guide for dream_test
  • Rewrote docs/contributing/testing.md - Contributor testing guidelines
  • Updated streaming guide test examples

Upgrading

Update your dependencies:

[dependencies]
dream = ">= 2.3.0 and < 3.0.0"

Then run:

gleam deps download

Migration Notes

If you depended on reversed parameter order:

If your code was working around the reversed parameter order bug, you'll need to update it. The correct order is now returned.

If you had extension patterns that weren't matching:

Routes like /products.{json,xml} now work as expected. No code changes needed unless you had workarounds.

Internal Changes

  • Removed gleeunit dependency
  • Added dream_test >= 1.0.3 dev dependency
  • Added dream_http_client >= 2.1.1 dev dependency (for test server polling)
  • New LiteralExtension segment type in router trie

Testing

All 239 tests pass with the new framework:

Summary: 239 run, 0 failed, 239 passed

Test categories:

  • Router: 85 tests (routing, params, wildcards, extensions, middleware)
  • HTTP: 98 tests (request, response, headers, cookies, validation)
  • Server: 24 tests (mist handler, request, response, lifecycle)
  • Streaming: 18 tests (body parsing, middleware, error handling)
  • Benchmarks: 6 tests (router performance)

Documentation

Community


Full Changelog: CHANGELOG.md

2.2.0

26 Nov 04:42
Immutable release. Only release title and notes can be modified.

Choose a tag to compare

Dream 2.2.0 Release Notes

Release Date: November 25, 2025

Dream 2.2.0 introduces comprehensive WebSocket support with a pub/sub broadcaster service, making it easy to build real-time applications with full server abstraction. This release also improves encapsulation of the Dream type, fixes several configuration bugs, and includes a 1,270+ line educational example with extensive integration tests.

Key Highlights

  • 🎯 WebSocket Support - Server-agnostic WebSocket API with no vendor lock-in
  • 📡 Pub/Sub Broadcaster - Generic OTP-based broadcaster service for real-time messaging
  • 🔒 Dream Type Encapsulation - Opaque type prevents coupling to internal implementation
  • 🔍 Configuration Accessors - New functions to inspect Dream instance settings
  • 🐛 Bug Fixes - Fixed bind() configuration persistence and type leakage issues
  • 📚 Comprehensive Example - 1,270+ line README with complete WebSocket chat app
  • Integration Tests - 9 Cucumber scenarios with CI/CD integration
  • 📖 Documentation Overhaul - Beginner-friendly rewrite with standardized terminology and examples
  • ⚙️ Sensible Defaults - 10MB max body size (was effectively infinite)

What's New

🆕 WebSocket Support

Dream now includes comprehensive WebSocket support with a server-agnostic API that completely abstracts away the underlying Mist implementation.

Key Features:

  • Upgrade HTTP requests to WebSocket connections
  • Handle text messages, binary messages, and custom application messages
  • Full connection lifecycle management (init, message, close)
  • Server-agnostic design - users never see Mist types
  • No closures required - explicit dependencies pattern
  • Pub/sub broadcasting service for fan-out messaging

New Module: dream/servers/mist/websocket

import dream/servers/mist/websocket

pub fn handle_chat_upgrade(request, context, services) {
  let dependencies = ChatDependencies(user: "Alice", services: services)
  
  websocket.upgrade_websocket(
    request,
    dependencies: dependencies,
    on_init: handle_init,
    on_message: handle_message,
    on_close: handle_close,
  )
}

WebSocket Types:

  • websocket.Connection - Opaque connection type (hides Mist)
  • websocket.Message(custom) - Text, Binary, Custom, or ConnectionClosed
  • websocket.Action(state, custom) - Continue or stop connection
  • websocket.SendError - Dream's own error type (not Mist's)

New Module: dream/services/broadcaster

A publish/subscribe broadcaster service for WebSocket and general fan-out messaging:

import dream/services/broadcaster

// Start a broadcaster at application startup
let assert Ok(chat_bus) = broadcaster.start_broadcaster()

// Subscribe to receive messages (typically in WebSocket init)
let channel = broadcaster.subscribe(chat_bus)
let selector = broadcaster.channel_to_selector(channel)

// Publish messages to all subscribers
broadcaster.publish(chat_bus, ChatMessage("Hello!"))

Broadcaster Features:

  • Generic OTP actor-based pub/sub service
  • Type-safe message distribution to multiple consumers
  • Integrates seamlessly with WebSocket selectors
  • Automatic subscriber cleanup on process termination
  • Suitable for chat rooms, notifications, live updates, etc.
  • Building block for more complex routing/partitioning logic

Complete Example:
See examples/websocket_chat/ for a full-featured chat application with:

  • Real-time messaging with pub/sub broadcasting
  • Join/leave notifications
  • Multiple concurrent users
  • Comprehensive integration tests
  • 1,270+ line README with step-by-step walkthrough
  • Complete frontend implementation (JavaScript, CSS)
  • Cucumber/Gherkin BDD-style integration tests

🔍 Dream Type Accessors

Added public accessor functions to inspect Dream instance configuration:

import dream/dream

let app = server.new()
  |> server.max_body_size(5_000_000)
  |> server.bind("0.0.0.0")

// Get configuration
let max_size = dream.get_max_body_size(app)  // Returns 5_000_000
case dream.get_bind_interface(app) {
  Some(interface) -> // Interface is configured
  None -> // Using default
}

Available Accessors:

  • dream.get_router() - Get configured router
  • dream.get_context() - Get configured context
  • dream.get_services() - Get configured services
  • dream.get_max_body_size() - Get max body size limit
  • dream.get_bind_interface() - Get configured bind interface
  • dream.get_server() - Get underlying server (requires risks_understood: True)

⚠️ About get_server():

The get_server() function requires explicit acknowledgment of risks:

// You MUST pass risks_understood: True
let mist_server = dream.get_server(app, risks_understood: True)

This function exposes Mist types and breaks Dream's abstraction. Passing False will panic. This is intentional - it forces you to consciously acknowledge that you're coupling your code to Mist.

Improvements

Dream Type Encapsulation

The Dream type is now opaque, preventing users from depending on internal server implementation details:

  • Users cannot construct Dream values directly
  • Users cannot access internal fields directly
  • All interaction is through builder functions and accessors
  • Prevents accidental coupling to Mist types
  • Internal code uses dream.create() with labeled arguments for construction
  • Framework provides controlled access through accessor functions

Why This Matters:

  • Ensures backward compatibility for future changes to internal structure
  • Makes it impossible to accidentally couple application code to Mist
  • Provides cleaner separation between Dream's public API and internal implementation
  • Allows Dream to change underlying server implementations without breaking user code

This is not a breaking change - users never constructed Dream directly, only through server.new().

Sensible Defaults

  • Max body size: Changed from effectively infinite (max_int64) to 10MB (10,000,000 bytes)
    • Prevents accidental memory exhaustion from unbounded request bodies
    • Can be overridden with server.max_body_size() for specific use cases

Internal Improvements

Dream Type Construction:

  • Added internal dream.create() constructor with labeled arguments
  • All internal Dream construction now uses explicit labeled parameters
  • Improves maintainability and reduces errors from positional argument changes
  • Provides single source of truth for Dream instance creation

Dependency Management:

  • Removed glisten dependency (was only used transiently via mist)
  • Dream now has zero direct dependencies on lower-level server libraries
  • All server types are properly abstracted through Dream's API

Bug Fixes

Fixed bind() Configuration Persistence

Bug: Calling server.bind("0.0.0.0") before server.listen() would lose the bind configuration.

Fix: Bind interface is now stored in the Dream type and properly applied during listen().

// Now works correctly!
server.new()
  |> server.router(my_router)
  |> server.bind("0.0.0.0")  // This configuration persists
  |> server.listen(3000)      // Server binds to 0.0.0.0

Fixed Vendor Lock-In in WebSocket API

Bug: WebSocket API was leaking Mist and Glisten types (mist.Text, glisten.SocketReason).

Fix: All types are now abstracted through Dream:

  • websocket.TextMessage instead of mist.Text
  • websocket.SendError instead of glisten.SocketReason
  • websocket.Connection (opaque) instead of mist.WebsocketConnection

Users never see Mist types, maintaining Dream's server-agnostic design.

Documentation

Beginner-Friendly Documentation Overhaul

Completely revised and standardized all documentation to be more accessible for developers new to Dream:

Consistency Improvements:

  • Standardized terminology across all documentation files
  • Aligned code examples to match actual implementation patterns
  • Fixed inconsistencies between different sections explaining the same concepts
  • Updated example READMEs with consistent formatting and structure
  • Ensured server implementation examples in docs match actual server code

Enhanced Learning Resources:

  • Improved quickstart guide with clearer explanations
  • Updated learning tutorials (hello-world, building-api, auth, advanced patterns)
  • Enhanced guides for controllers, templates, testing, streaming, and operations
  • Better architecture and design principle documentation
  • More detailed examples documentation

Example Code Improvements:

  • Updated rate_limiter example with better patterns
  • Enhanced static file serving example documentation
  • Improved streaming_capabilities example clarity
  • All examples now follow consistent patterns and conventions

Impact:

  • ~830 lines added, ~374 lines removed across 24 documentation files
  • Every major documentation section touched for consistency
  • Removed confusing terminology and ambiguous explanations
  • Made it easier for newcomers to understand Dream's patterns

WebSocket Chat Example

Added comprehensive WebSocket example with extensive documentation:

  • 1,270+ line README covering:
    • What WebSockets are and when to use them (vs. polling, SSE, etc.)
    • Complete architectural overview with data flow diagrams
    • Step-by-step walkthrough of every component (services, controllers, views, templates)
    • Message lifecycle from user input → WebSocket → pub/sub → all clients
    • Frontend JavaScript implementation explained line-by-line
    • CSS styling and modern UI patterns explained
    • Deep dive into Dream's no-closure pattern and why it matters
    • Server-agnostic design philosophy and abstraction benefits
    • Common patterns: room management, user presence, message hist...
Read more

2.1.0

24 Nov 23:01

Choose a tag to compare

Dream 2.1.0 Release Notes

Release Date: November 24, 2025

Dream 2.1.0 introduces the new dream_mock_server module for testing HTTP clients and a major overhaul of dream_http_client with message-based streaming support, making it fully compatible with OTP actors and concurrent operations.

What's New

🆕 dream_mock_server 1.0.0

A general-purpose HTTP mock server for testing HTTP clients without external dependencies. Provides both streaming and non-streaming endpoints with programmatic control.

Key Features:

  • Programmatic control: start(port) and stop(handle) functions
  • 10 non-streaming endpoints for various test scenarios
  • 4 streaming endpoints for testing chunked responses
  • Full Cucumber integration test suite
  • Comprehensive HexDocs documentation

Non-Streaming Endpoints:

  • GET / - Info page with endpoint documentation
  • GET /get - Echo query parameters
  • POST /post - Echo request body
  • GET /status/:code - Return specified HTTP status code
  • GET /json - Sample JSON response
  • GET /text - Plain text response
  • GET /uuid - Generate and return UUID
  • GET /large - Large response (1MB) for memory testing
  • GET /empty - Empty response body
  • GET /slow - Delayed response (2s) for timeout testing

Streaming Endpoints:

  • GET /stream/fast - 10 chunks at 100ms intervals
  • GET /stream/slow - 5 chunks at 2s intervals
  • GET /stream/burst - Random burst pattern (5-10 chunks, 100-500ms delays)
  • GET /stream/huge - 100 chunks for memory/performance testing

Example Usage:

import dream_mock_server/server

pub fn test_http_client() {
  let assert Ok(mock) = server.start(3004)
  
  // Make requests to localhost:3004
  let result = http_client.get("http://localhost:3004/json")
  
  server.stop(mock)
}

🚨 dream_http_client 2.0.0 - BREAKING CHANGES

Major overhaul with message-based streaming, OTP actor support, and comprehensive error handling improvements.

Breaking Change 1: Module Consolidation

The fetch and stream modules have been consolidated into a unified client module.

Migration:

// Before (v1.x)
import dream_http_client/fetch
import dream_http_client/stream

let body = client.new
  |> client.host("api.example.com")
  |> fetch.request()

let chunks = client.new
  |> client.host("cdn.example.com")
  |> stream.stream_request()

// After (v2.0)
import dream_http_client/client

let body = client.new
  |> client.host("api.example.com")
  |> client.send()

let chunks = client.new
  |> client.host("cdn.example.com")
  |> client.stream_yielder()

Breaking Change 2: stream_yielder() Return Type

The stream_yielder() function now returns Result values to properly surface errors.

Migration:

// Before (v1.x)
client.new
  |> host("api.example.com")
  |> stream.stream_request()
  |> yielder.each(fn(chunk) {
    process(chunk)
  })

// After (v2.0)
client.new
  |> host("api.example.com")
  |> client.stream_yielder()
  |> yielder.each(fn(result) {
    case result {
      Ok(chunk) -> process(chunk)
      Error(reason) -> handle_error(reason)
    }
  })

Breaking Change 3: StreamMessage Type

Added DecodeError variant to StreamMessage for explicit FFI corruption handling.

Migration:

// If you pattern match on StreamMessage, add DecodeError handling:
case msg {
  StreamStart(req_id, headers) -> ...
  Chunk(req_id, data) -> ...
  StreamEnd(req_id, headers) -> ...
  StreamError(req_id, reason) -> ...
  DecodeError(reason) -> ...  // NEW - handle FFI corruption
}

// Or use a catch-all:
case msg {
  Chunk(req_id, data) -> process(data)
  _ -> continue(state)
}

New Features

Message-Based Streaming (dream_http_client 2.0.0)

Added full OTP actor support for concurrent HTTP streams.

New API:

  • stream_messages() - Start a message-based stream, returns RequestId
  • StreamMessage type - Stream lifecycle messages sent to your mailbox
  • select_stream_messages() - Integrate with OTP selectors
  • cancel_stream() - Cancel active streams

Example:

import dream_http_client/client.{
  type StreamMessage, Chunk, StreamEnd, StreamError, StreamStart,
  select_stream_messages
}
import gleam/otp/actor.{continue}
import gleam/erlang/process.{new_selector}

pub type Message {
  HttpStream(StreamMessage)
}

fn init_selector() {
  new_selector()
  |> select_stream_messages(HttpStream)
}

fn handle_message(msg: Message, state: State) {
  case msg {
    HttpStream(Chunk(req_id, data)) -> process_chunk(data, state)
    HttpStream(StreamEnd(req_id, _)) -> cleanup(req_id, state)
    HttpStream(StreamError(req_id, reason)) -> handle_error(state)
    HttpStream(StreamStart(_, _)) -> continue(state)
    HttpStream(DecodeError(reason)) -> log_corruption(reason, state)
  }
}

Configurable Timeout (dream_http_client 2.0.0)

All HTTP requests now support configurable timeouts.

Example:

import dream_http_client/client.{timeout}

client.new
  |> host("slow-api.example.com")
  |> timeout(5000)  // 5 second timeout
  |> send()

Default timeout is 600 seconds (10 minutes) if not specified.

Improved Error Handling (dream_http_client 2.0.0)

All errors now include full context:

  • No more discarded errors (Error(_) patterns eliminated)
  • Decode errors include underlying reasons
  • All error messages are actionable and user-friendly
  • No panic calls - all errors return gracefully

Before:

// Errors were silently discarded or returned as generic messages
Error("Request failed")

After:

// Errors include full context
Error("Failed to decode headers: Expected string, found integer at position 2")
Error("Stream failed: Connection timeout after 30000ms")

Documentation

New Governance Files

  • CONTRIBUTING.md - Quick start guide for contributors
  • CODE_OF_CONDUCT.md - Community standards (Contributor Covenant 2.1)
  • SECURITY.md - Security policy clarifying Dream's responsibilities as a library
  • GitHub issue templates (bug reports, feature requests)
  • GitHub pull request template

Documentation Improvements

  • Moved TESTING.md to docs/contributing/testing.md
  • Updated architecture docs with correct dream_http_client API
  • Added "About Dream" section explaining TrustBound's role
  • All dream_http_client examples updated to use new API

Bug Fixes

dream_http_client 2.0.0

  • Fixed send() for non-streaming requests: Now uses synchronous httpc mode. Previously used streaming mode which couldn't handle Content-Length responses
  • Fixed URL port handling: stream_yielder() and stream_messages() now correctly include port in URLs
  • Fixed message routing: Streaming messages now sent to correct process
  • Fixed header decoding: Errors propagate instead of being hidden
  • Fixed RequestId handling: FFI corruption returns DecodeError variant instead of crashing

Testing

dream_http_client 2.0.0

  • All tests now use dream_mock_server instead of external dependencies
  • Configurable test port via MOCK_SERVER_PORT environment variable
  • Added unit tests for malformed header handling
  • Added unit test for timeout() builder function

dream_mock_server 1.0.0

  • Comprehensive Cucumber integration test suite
  • Unit tests for port configuration and lifecycle
  • Tests for multiple concurrent servers
  • Tests for server restart and idempotent stop

Internal Changes

Refactored FFI Boundary (dream_http_client 2.0.0)

Erlang now handles all raw httpc message parsing and normalization:

  • Erlang FFI performs pattern matching on raw messages
  • Erlang normalizes headers (charlist to binary)
  • Gleam receives clean, simplified data structures
  • Clear separation of concerns between layers

Code Quality (dream_http_client 2.0.0)

  • Flattened all nested case expressions
  • Extracted all anonymous functions to named helpers
  • Consistent error handling patterns throughout
  • Comprehensive inline documentation

Upgrading

Update your dependencies:

[dependencies]
dream = ">= 2.1.0 and < 3.0.0"
dream_http_client = ">= 2.0.0 and < 3.0.0"

[dev-dependencies]
dream_mock_server = ">= 1.0.0 and < 2.0.0"

Then run:

gleam deps download

Migration Checklist

  1. ✅ Update imports: dream_http_client/fetchdream_http_client/client
  2. ✅ Update imports: dream_http_client/streamdream_http_client/client
  3. ✅ Rename function calls: fetch.request()client.send()
  4. ✅ Rename function calls: stream.stream_request()client.stream_yielder()
  5. ✅ Update stream_yielder() usage to handle Result type
  6. ✅ Add DecodeError handling to StreamMessage pattern matches (or use catch-all)
  7. ✅ Consider using stream_messages() for OTP actors with concurrent streams
  8. ✅ Replace external mock server dependencies with dream_mock_server
  9. ✅ Run gleam test to verify all tests pass

Acknowledgements

Special thanks to Louis Pilfold for bringing to our attention that the HTTP client did not support message-based streaming for OTP actors. This feedback led to the comprehensive overhaul of dream_http_client in this release, making it fully compatible with OTP patterns and concurrent operations.

Documentation

All packages are available with updated documentation on HexDocs:

Read more

2.0.0

23 Nov 11:12
9bef0ef

Choose a tag to compare

Dream 2.0.0 Release Notes

Release Date: November 23, 2025

Dream 2.0.0 is a major release that rewrites the router using a radix trie data structure, delivering dramatic performance improvements while maintaining Dream's ergonomic API. This release also simplifies the developer experience by introducing EmptyContext and EmptyServices as defaults.

🚨 Breaking Changes

Router API Change

The router constant has been changed to a function router() to support the new radix trie implementation.

Migration:

// Before (v1.x)
import dream/router.{router}
let app_router = router |> route(...)

// After (v2.0)
import dream/router.{router}
let app_router = router() |> route(...)

Why: The radix trie requires runtime initialization with dict.new(), which cannot be used in Gleam constants. This breaking change was necessary to achieve O(path depth) performance.

Default Context and Services

server.new() now defaults to EmptyContext and EmptyServices instead of requiring AppContext.

Migration (if you don't use custom context/services):

// Before (v1.x)
import dream/context.{AppContext}

server.new()
|> context(AppContext(request_id: "..."))
|> services(EmptyServices)
|> router(app_router)

// After (v2.0) - Much simpler!
server.new()
|> router(app_router)
|> listen(3000)

Migration (if you DO use custom context/services):

// Before (v1.x)
import dream/context.{AppContext}

server.new()
|> context(AppContext(request_id: "..."))
|> router(app_router)

// After (v2.0) - Explicit context() call still works
import dream/context.{type AppContext, new_context}

server.new()
|> context(new_context("req-123"))
|> router(app_router)

Why: Most applications don't need per-request context. This change reduces boilerplate and makes simple applications even simpler.

Performance Improvements

Radix Trie Router

The router has been completely rewritten using a radix trie data structure, replacing the previous linear search with O(path depth) lookup.

Benchmark Results (100 routes):

Scenario Before (Linear) After (Radix Trie) Improvement
First route match 135.0μs 0.56μs 241x faster
Middle route match 70.0μs 1.44μs 49x faster
Last route match 7.3μs 1.35μs 5.4x faster
Route not found 2500.0μs 1.85μs 1353x faster

Scalability:

The radix trie delivers consistent ~1.3-1.5μs lookup times regardless of route count. Whether you have 100 routes or 1000 routes, performance remains constant. The router is now fast enough that it will never be your bottleneck.

New Features

EmptyContext Type

Added EmptyContext for applications that don't require per-request context:

import dream/context.{type EmptyContext}
import dream/servers/mist/server

pub fn main() {
  server.new()  // Defaults to EmptyContext and EmptyServices
  |> router(my_router())
  |> listen(3000)
}

Extension Stripping for Content Negotiation

Routes without explicit extensions now match paths with extensions, enabling controllers to handle format detection:

// Route definition
router()
|> route(method: Get, path: "/products/:id", controller: show_product, middleware: [])

// Matches both:
// - /products/1 → id = "1", format = None
// - /products/1.json → id = "1.json", format = Some("json")

fn show_product(request: Request, context: Context, services: Services) -> Response {
  use id_param <- result.try(require_string(request, "id"))
  
  case id_param.format {
    Some("json") -> json_response(ok, product_data)
    Some("csv") -> csv_response(ok, product_data)
    _ -> html_response(ok, product_view)
  }
}

Explicit extension patterns (e.g., *.{jpg,png}) take precedence over extension stripping.

Parameter Name Remapping

Routes can now use different parameter names at the same position without conflict:

router()
|> route(method: Get, path: "/users/:id", controller: show_user, middleware: [])
|> route(method: Get, path: "/users/:user_id/posts", controller: user_posts, middleware: [])

// Both routes work correctly:
// - /users/123 → extracts "id=123"
// - /users/456/posts → extracts "user_id=456"

The router preserves each route's original parameter names and remaps them after lookup.

Code Quality Improvements

Eliminated Nested Cases

All nested case statements have been removed across the codebase:

  • dream/http/transaction: Refactored get_header and get_cookie to use recursive helpers
  • dream/router/trie: Refactored lookup_in_node to extract nested logic
  • Test helper functions refactored to use flat case structures

Removed Anonymous Functions

All anonymous functions have been extracted to named helpers:

  • dream/servers/mist/handler: create_request_handler
  • dream/router: apply_middleware_to_controller
  • dream/http/transaction: header_name_mismatch, cookie_name_mismatch

Fixed All Compiler Warnings

  • Removed unused imports across all modules
  • Fixed redundant tuples
  • Removed unused function arguments
  • Added proper underscores for intentionally unused parameters

New Modules

dream/router/trie

Complete radix trie implementation with comprehensive inline documentation:

  • new() - Create a new empty trie
  • insert() - Insert a route into the trie
  • lookup() - Look up a path and extract parameters
  • Supports all Dream routing features: literals, parameters, wildcards, multi-wildcards, extension patterns

dream/router/parser

Path pattern parser for converting string paths to structured segments:

  • parse_path() - Parse a path string into segments
  • Segment type: Literal, Param, SingleWildcard, MultiWildcard, ExtensionPattern

Documentation Updates

Performance Information

All router documentation now explains radix trie performance:

  • O(path depth) lookup complexity
  • Consistent ~1.3-1.5μs performance regardless of route count
  • Benchmarks showing 100-1000x improvements for common scenarios

Type Safety Trade-off

Documentation now clearly explains Dream's design decision:

  • ✅ Controllers ARE type-checked (context, services types verified by compiler)
  • ❌ Path parameters are validated at runtime, not compile-time
  • This favors ergonomic APIs over compile-time guarantees
  • Links to Discussion #15 for type-safe routing exploration

Updated Examples

  • All 8 example applications updated to use router() function
  • Examples simplified by removing unnecessary context() and services() calls
  • Controllers and middleware use full parameter names (request, context, services)
  • README example includes comprehensive comments explaining middleware and controller patterns

Bug Fixes

  • Multi-wildcard routes now correctly match zero segments (e.g., /public/ matches /public/**filepath)
  • Extension stripping preserves original parameter values for format detection
  • Parameter name consistency when routes share parameter positions
  • Database example integration tests (parameter flow fixed)
  • Multi-format example PostgreSQL connection (port mismatch resolved)

Internal Architecture Changes

Centralized Route Execution

Added dream.execute_route() to centralize route execution logic:

  • Handler now passes extracted parameters directly to execute_route()
  • Avoids redundant find_route() calls
  • Improved performance and code clarity

Removed Deprecated Functions

The following deprecated functions have been removed:

  • router.method() - Use labeled arguments in route() instead
  • router.path() - Use labeled arguments in route() instead
  • router.controller() - Use labeled arguments in route() instead
  • router.middleware() - Use labeled arguments in route() instead
  • router.match_path() - Use find_route() instead

Upgrading

Update your dependencies to 2.0.0:

[dependencies]
dream = ">= 2.0.0 and < 3.0.0"

Then run:

gleam deps download

Migration Checklist

  1. ✅ Change all router references to router() in your code
  2. ✅ Remove unnecessary context() and services() calls (if not using custom context/services)
  3. ✅ If using AppContext, import it explicitly: import dream/context.{type AppContext, new_context}
  4. ✅ Run gleam test to verify all tests pass
  5. ✅ Run your integration tests to ensure routing still works correctly

Testing

This release includes comprehensive test coverage:

  • 62+ router unit tests covering all pattern types, parameter remapping, extension stripping, and edge cases
  • Performance benchmark tests comparing radix trie vs linear router
  • Integration tests for all 8 example applications
  • All tests pass on CI with PostgreSQL integration

Documentation

All packages are available with updated documentation on HexDocs:

Community

Acknowledgments

Special thanks to Louis Pilfold for suggesting the radix trie approach that made th...

Read more

1.0.2

22 Nov 03:31
20a785f

Choose a tag to compare

Dream 1.0.2 Release Notes

Release Date: November 21, 2025

Dream 1.0.2 is a documentation-focused release that significantly improves the README and adds new concept documentation to make Dream more accessible to newcomers.

What's Changed

README Refactor

The README has been completely rewritten to be more accessible and scannable:

  • Reduced from 520 lines to ~130 lines - Much easier to scan and understand
  • Zero-knowledge friendly - Explains what Dream, Gleam, and BEAM are for complete beginners
  • Complete working example - Shows a full server with line-by-line explanations
  • Better structure - Quick introduction with links to detailed docs

Before: Assumed readers knew what Dream, Gleam, and BEAM were, showed type signatures instead of working examples, and was too dense to scan quickly.

After: Starts from zero knowledge, shows working code with explanations, and points to detailed docs for deeper dives.

New Documentation

Added standalone documentation files for better organization:

All detailed technical content that was in the README has been moved to these dedicated files, making the README a quick introduction that points to the right docs.

Module Documentation Updates

All module README files now include HexDocs documentation badges for quick access to API documentation:

  • dream_config
  • dream_ets
  • dream_http_client
  • dream_json
  • dream_opensearch
  • dream_postgres

Terminology Fixes

Clarified the distinction between:

  • Controller - A module/file containing multiple actions (e.g., controllers/users_controller.gleam)
  • Controller Action - A function within that controller that handles a request (e.g., index, show)

This distinction is now consistently used throughout the documentation.

Upgrading

Update your dependencies to 1.0.2:

[dependencies]
dream = ">= 1.0.2 and < 2.0.0"
dream_config = ">= 1.0.2 and < 2.0.0"
dream_ets = ">= 1.0.2 and < 2.0.0"
dream_http_client = ">= 1.0.2 and < 2.0.0"
dream_json = ">= 1.0.2 and < 2.0.0"
dream_opensearch = ">= 1.0.2 and < 2.0.0"
dream_postgres = ">= 1.0.2 and < 2.0.0"

Then run:

gleam deps download

Note: This is a documentation-only release. No code changes were made. Upgrading is optional but recommended for access to improved documentation.

No Breaking Changes

This release contains no breaking changes or code modifications. All functionality remains identical to 1.0.1.

Documentation

All packages are available with updated documentation on HexDocs:


Full Changelog: CHANGELOG.md

1.0.1

22 Nov 01:24

Choose a tag to compare

Dream 1.0.1 Release Notes

Release Date: November 22, 2025

Dream 1.0.1 is a patch release that fixes logo display issues on Hex.pm for all Dream packages. This ensures proper branding and visual consistency when viewing package documentation on Hex.pm.

What's Fixed

Logo Display on Hex.pm

Fixed broken logo images on Hex.pm package pages by updating README files to use full GitHub URLs instead of relative paths.

Changed in all packages:

  • Main dream package
  • dream_config
  • dream_ets
  • dream_http_client
  • dream_json
  • dream_opensearch
  • dream_postgres

Technical Details:

  • Updated logo image source from relative path (ricky_and_lucy.png) to full GitHub URL (https://raw.githubusercontent.com/TrustBound/dream/main/ricky_and_lucy.png)
  • Added Dream logo to all module README files for consistent branding across the ecosystem
  • Logos now display correctly on Hex.pm, GitHub, and any other platform rendering the README

Upgrading

Update your dependencies to 1.0.1:

[dependencies]
dream = ">= 1.0.1 and < 2.0.0"
dream_config = ">= 1.0.1 and < 2.0.0"
dream_ets = ">= 1.0.1 and < 2.0.0"
dream_http_client = ">= 1.0.1 and < 2.0.0"
dream_json = ">= 1.0.1 and < 2.0.0"
dream_opensearch = ">= 1.0.1 and < 2.0.0"
dream_postgres = ">= 1.0.1 and < 2.0.0"

Then run:

gleam deps download

Note: This is a documentation-only release. No code changes were made. Upgrading is optional but recommended for consistency with published documentation.

No Breaking Changes

This release contains no breaking changes or code modifications. All functionality remains identical to 1.0.0.

Documentation

All packages are available with updated documentation on HexDocs:


Full Changelog: CHANGELOG.md