This document outlines the planned future milestones and technical architecture for upcoming releases of BeamExport.
🎯 Release Horizons
v0.1.0 (Current) v0.2.0 v0.3.0 v0.4.0
┌───────────────────────┐ ┌───────────────────────┐ ┌───────────────────────┐ ┌───────────────────────┐
│ • Production CSV Core │ ──> │ • Cloud Storage Sinks │ ──> │ • Additional Formats │ ──> │ • Oban Background │
│ • Ecto Cursor Stream │ │ (S3, GCS, R2) │ │ (NDJSON, Parquet) │ │ Worker Blueprint │
│ • Plug Staged/Chunked │ │ • Multipart Streaming │ │ • Pluggable Format │ │ • Dialyzer in CI │
│ • Hardened Security │ │ • Zero-Disk Uploads │ │ Architecture │ │ • Progress Webhooks │
└───────────────────────┘ └───────────────────────┘ └───────────────────────┘ └───────────────────────┘☁️ Milestone 1 (v0.2.0): Cloud Object Storage Sinks
Goal
Directly stream large datasets to AWS S3, Google Cloud Storage, Cloudflare R2, or MinIO via S3-compatible multipart uploads without requiring intermediate local disk storage.
Planned Architecture: BeamExport.Destination.S3
┌─────────────────┐ ┌─────────────────┐ ┌────────────────────────┐ ┌─────────────────┐
│ Ecto DB Cursor │ ────> │ CSV/Gzip Stream │ ────> │ 5MB Part Buffer Stream │ ────> │ S3 Multipart │
│ (Repo.stream) │ │ (iodata chunks) │ │ (Stream.chunk_while) │ │ Upload Parts │
└─────────────────┘ └─────────────────┘ └────────────────────────┘ └─────────────────┘Proposed API
{:ok, meta} =
BeamExport.to_s3(
{MyApp.Repo, query},
bucket: "company-exports",
key: "exports/users_2026.csv.gz",
s3_client: MyApp.S3Client, # e.g. ExAws.S3 or ReqS3
compress: :gzip,
part_size_mb: 5,
headers: ["ID", "Email", "Name"],
mapper: fn u -> [u.id, u.email, u.name] end
)Key Considerations
- Minimum Part Size: S3 requires all multipart parts (except the last) to be $\ge 5\text{ MB}$. The pipeline will accumulate iodata up to 5 MB per part before dispatching asynchronously.
- Connection Isolation: Database cursor is held only while reading and uploading chunks; once complete, the transaction commits and the multipart upload finalizes.
- Error Abort: If an error occurs mid-stream,
AbortMultipartUploadis automatically invoked to prevent orphaned, billed storage parts.
📊 Milestone 2 (v0.3.0): Multi-Format Extensibility (NDJSON & Parquet)
Goal
Extend BeamExport beyond RFC 4180 CSV to support modern machine learning and analytics data interchange formats.
Target Formats
Newline-Delimited JSON (
NDJSON/.jsonl):- High-throughput streaming of structured JSON objects.
- Built on
Jason.encode_to_iodata!/1with zero extra string allocations. - Ideal for log exports, Elasticsearch indexing, and BigQuery data ingestion.
Apache Parquet (
.parquet):- Columnar storage format for analytical queries (DuckDB, Snowflake, ClickHouse, Pandas).
- High compression ratio and typed schema definitions.
Proposed API
# Streaming NDJSON export:
BeamExport.to_file(
{MyApp.Repo, query},
"exports/events.ndjson.gz",
format: :ndjson,
mapper: fn event -> %{id: event.id, type: event.type, payload: event.data} end
)⚙️ Milestone 3 (v0.4.0): Oban Background Worker Blueprint
Goal
Provide a production-ready, turnkey Oban worker behavior and recipe for handling asynchronous background export workflows.
Planned Features: BeamExport.Oban.Worker
- Standardized Job Lifecycle: Automatic state transitions (
queued$\to$exporting$\to$uploaded$\to$completed/failed). - Telemetry-Driven Progress: Emits chunk progress events so web apps can display live export progress bars over Phoenix LiveView or WebSockets.
- Signed URL Delivery: Generates short-lived pre-signed download URLs (e.g. valid for 24 hours) upon completion and triggers email or webhook notifications.
- Resource Limits: Built-in concurrency rate-limiting across Oban queues to prevent concurrent export jobs from starving primary application database pools.
Proposed Usage
defmodule MyApp.Workers.UserExportWorker do
use BeamExport.Oban.Worker,
repo: MyApp.Repo,
storage: :s3,
bucket: "my-app-exports"
@impl BeamExport.Oban.Worker
def build_query(%{"organization_id" => org_id}) do
from(u in MyApp.User, where: u.org_id == ^org_id)
end
@impl BeamExport.Oban.Worker
def mapper(user), do: [user.id, user.email, user.name]
end🔍 Milestone 4: CI & Static Analysis (Dialyzer)
Goal
Ensure comprehensive static type safety and contract verification across all optional dependency configurations.
Action Items
- Add
dialyxirtomix.exs:devand:testenvironments. - Generate and cache Dialyzer Persistent Lookup Tables (PLTs) in GitHub Actions (
.github/workflows/ci.yml). - Enforce clean typespec verification with zero Dialyzer warnings in pull request checks.