Skip to content

Conversation

@dcrockwell
Copy link
Contributor

dream_http_client 3.0.0 Release

Overview

Major redesign of the HTTP client streaming API with process isolation, callback-based handling, and type-safe headers throughout the module.

Key Changes

1. Redesigned Streaming API (Breaking)

Old approach: Manual selector loops, 40+ lines of boilerplate
New approach: Callback-based with dedicated processes

// Now:
let assert Ok(stream) = client.new
  |> client.on_stream_chunk(fn(data) { io.print(data) })
  |> client.start_stream()

client.await_stream(stream)

Benefits:

  • No selector boilerplate
  • Process isolation (clean mailboxes)
  • Follows BEAM best practices
  • Simpler for users

2. Header Type (Breaking)

Added Header(name, value) type for type safety throughout module.

3. Immediate Recording Saves

Recordings now saved immediately when captured - never lose data even if process crashes.

4. Tested Snippets

All README examples are now tested code in test/snippets/ following dream_ets pattern.

Migration

See CHANGELOG.md and Release Notes for complete migration guide.

Testing

  • ✅ 97 tests passing
  • ✅ All examples tested via snippets
  • ✅ Pre-commit checks passed
  • ✅ All modules and examples build without warnings

Breaking Changes

  • stream_messages() removed from public API
  • select_stream_messages() removed from public API
  • get_headers() returns List(Header) not tuples
  • StreamStart/StreamEnd use List(Header)

Ready for release to hex.pm.

dcrockwell and others added 2 commits December 9, 2025 16:55
… add Header type

## Why This Change Was Made

- The old stream_messages() API exposed selector boilerplate to users, violating ergonomics principles
- Users had to write 40+ lines of recursive receive loops for every stream
- Mailbox pollution: HTTP messages mixed with application messages
- Not following BEAM best practices (one process per concern)
- Header tuples lacked type safety throughout the module
- Recordings were lost if stop() wasn't called (original issue on this branch)

## What Was Changed

Breaking Changes:
- Removed stream_messages() and select_stream_messages() from public API (now internal)
- Added start_stream() with builder-based callbacks (on_stream_start, on_stream_chunk, on_stream_end, on_stream_error)
- Each stream runs in dedicated BEAM process for isolation
- Added Header type replacing List(#(String, String)) throughout module
- get_headers() now returns List(Header)
- StreamStart/StreamEnd use List(Header) for headers
- add_header(name, value) still takes strings (ergonomic builder pattern)

New API:
- start_stream() - spawn stream with callbacks
- await_stream(handle) - wait for completion
- cancel_stream_handle(handle) - cancel stream
- is_stream_active(handle) - check status
- Header(name, value) type for type safety

Recording (original feature):
- Recordings saved immediately when captured
- recorder.stop() now optional
- Added storage.save_recording_immediately()

Documentation:
- All README examples are tested snippets (dream_ets pattern)
- Added test/snippets/ directory with 9 tested examples
- World-class hexdocs for Header and StreamHandle types
- Comprehensive migration guide in CHANGELOG

Tests:
- 97 tests passing (was 112, deleted 25 obsolete selector tests, added 10 new start_stream tests)
- Deleted stream_messages_integration_test.gleam and stream_messages_unit_test.gleam (obsolete)
- Added start_stream_test.gleam with callback API tests

## Note to Future Engineer

- The Header type uses .name and .value fields, not tuples. Convert at FFI boundaries.
- Callbacks execute in the SPAWNED process, not the caller's process. Send messages to parent if needed.
- await_stream() polls with sleep(50) - no timeout, it waits forever. Use cancel_stream_handle() for timeout behavior.
- ETS tables must be initialized before spawning (ensure_ets_tables() in start_stream). Yes, this bit me.
- stream_messages() still exists internally - it's used by start_stream(). Don't delete it.

If you're wondering why we didn't just improve the selector API instead of replacing it entirely: we tried. The fundamental issue is that putting HTTP messages in the user's mailbox is the wrong abstraction. One process per stream is the BEAM way. Should have been designed this way from the start, but better late than never.

Enjoy not writing recursive receive loops anymore. You're welcome.
HTTP Client: Simpler Streaming with Callbacks and Type-Safe Headers
@dcrockwell dcrockwell self-assigned this Dec 10, 2025
@dcrockwell dcrockwell added documentation Improvements or additions to documentation enhancement New feature or request release Official public releases labels Dec 10, 2025
@dcrockwell dcrockwell merged commit e27869a into main Dec 10, 2025
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation enhancement New feature or request release Official public releases

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants