This document explains the architectural structure of brass-runtime, how the layers relate to each other, and the design principles behind them.
The architecture is intentionally inspired by ZIO 2, but implemented from scratch in TypeScript, without relying on Promise as the semantic runtime primitive.
┌────────────────────────────────────────────┐
│ User Programs │
│ (examples, apps, libraries, tests) │
└────────────────────────────────────────────┘
▲
│
┌────────────────────────────────────────────┐
│ High-level Modules │
│ (HTTP, Streams, Resources, etc.) │
└────────────────────────────────────────────┘
▲
│
┌────────────────────────────────────────────┐
│ Core Effect Runtime │
│ Async / Effect / Fiber / Scope / Scheduler │
└────────────────────────────────────────────┘
▲
│
┌────────────────────────────────────────────┐
│ JavaScript Host Environment │
│ (event loop, timers, fetch, callbacks) │
└────────────────────────────────────────────┘
Key idea:
Only the core runtime knows about execution.
Everything else is pure descriptions interpreted by the runtime.
Promiseis never the semantic primitive.- Async work is represented explicitly as data (
Async). - The runtime interprets async effects using callbacks and schedulers.
This ensures:
- Deterministic scheduling
- Cooperative cancellation
- Structured concurrency
- Testability
All effects are lazy:
- Nothing runs until interpreted by a
Fiber - Creating an effect is pure and side-effect free
const eff = http.getJson<Post>("/posts/1")
// nothing has happened yetExecution only begins when:
- Forked into a fiber
- Or awaited via
toPromise(interop helper)
Fibers always belong to a Scope.
Rules:
- Child fibers cannot outlive their parent scope
- Closing a scope interrupts all children
- Finalizers run in LIFO order
This prevents:
- Leaked async tasks
- Forgotten cleanups
- Detached background work
Async
│
▼
Fiber ── Scheduler
│
▼
Scope ── Finalizers
- Algebraic data type representing effectful computation
- Variants:
Succeed | Fail | Sync | Async | FlatMap - Pure data, no execution
- Interpreter of
Async - Owns:
- Stack
- RunState
- Interrupt status
- Finalizers
- Can be:
- Joined
- Interrupted
- Forked
- Cooperative task queue
- Ensures fairness
- No preemption
- Explicit scheduling boundaries
- Lifetime manager
- Owns:
- Child fibers
- Sub-scopes
- Finalizers
- Deterministic cleanup
Streams are pull-based, inspired by ZIO Streams.
ZStream
│
▼
Pull<R, Option<E>, A>
- Backpressure-aware
- Resource-safe
- Scope-bound
- Deterministic cleanup
- Simpler cancellation semantics
- Natural backpressure
- Easier reasoning about lifetimes
HTTP is not part of the core runtime.
It is implemented as a high-level module built entirely on Async.
HttpRequest
│
▼
HttpClient (Request => Async)
│
▼
Middlewares
(withMeta, logging, retries)
│
▼
Content helpers
(getJson, getText)
- Fully lazy
- Cancelable via fiber interruption
- Composable via middleware functions
- Environment-aware (baseUrl, headers, auth)
HTTP execution only happens when:
- The returned
Asyncis run by a fiber
Middlewares are pure functions:
type Middleware = (client: HttpClient) => HttpClientThey:
- Do not execute effects
- Only transform descriptions
- Compose left-to-right
Examples:
withMeta- logging
- retries
- tracing
- auth injection
- Thin boundary for examples and DX
- Not part of core semantics
- Converts fiber execution into a
Promise
- Adapts Promise APIs that support
AbortSignal - Preserves cooperative cancellation
- Integrates cleanly with fibers
Interop is explicit and contained.
By design:
- ❌ Hidden Promises inside effects
- ❌ Detached async work
- ❌ Implicit background execution
- ❌ Fire-and-forget APIs
If something runs:
It must be owned by a fiber and a scope.
Designed to grow horizontally:
- New modules (HTTP, FS, DB, etc.)
- New stream combinators
- New schedulers (priority, test)
- New runtimes (browser, workers)
Without changing the core semantics.
- Core = execution + rules
- Async = description
- Fiber = interpreter
- Scope = lifetime
- Modules = pure libraries on top
- Interop = explicit escape hatch
If you understand this: 👉 you understand the entire system.
This architecture is:
- Experimental
- Intentionally minimal
- Designed for learning, exploration, and correctness
But:
It already enforces stronger guarantees than most Promise-based codebases.
MIT License © 2025