ZIO Blocks
Modular, zero-dependency building blocks for modern Scala applications.
What Is ZIO Blocks?β
ZIO Blocks is a family of type-safe, modular building blocks for Scala applications. Each block is a standalone library with zero or minimal dependencies, designed to work with any Scala stackβZIO, Cats Effect, Kyo, Ox, Akka, or plain Scala.
The philosophy is simple: use what you need, nothing more. Each block is independently useful, cross-platform (JVM, JS), and designed to compose with other blocks or your existing code.
The Blocksβ
| Block | Description | Status |
|---|---|---|
| Schema | Type-safe schemas with automatic codec derivation | β Available |
| Chunk | High-performance immutable indexed sequences | β Available |
| Scope | Compile-time safe resource management and DI | β Available |
| Docs | GitHub Flavored Markdown parsing and rendering | β Available |
| TypeId | Compile-time type identity with rich metadata | β Available |
| Context | Type-indexed heterogeneous collections | β Available |
| Streams | Pull-based streaming primitives | π§ In Development |
Core Principlesβ
- Zero Lock-In: No dependencies on ZIO, Cats Effect, or any effect system. Use with whatever stack you prefer.
- Modular: Each block is a separate artifact. Import only what you need.
- Cross-Platform: Full support for JVM and Scala.js.
- Cross-Version: Full support for Scala 2.13 and Scala 3.x with source compatibilityβadopt Scala 3 on your timeline, not ours.
- High Performance: Optimized implementations that avoid boxing, minimize allocations, and leverage platform-specific features.
- Type Safety: Leverage Scala's type system for correctness without runtime overhead.
Schemaβ
The Schema block brings dynamic-language productivity to statically-typed Scala. Define your data types once, and derive codecs, validators, optics, and more automatically.
The Problemβ
In statically-typed languages, you often maintain separate codec implementations for each data format (JSON, Avro, Protobuf, etc.). Meanwhile, dynamic languages handle data effortlessly:
// JavaScript: one line and done
const data = await res.json();
In Scala, you'd typically need separate codecs for each formatβa significant productivity gap.
The Solutionβ
ZIO Blocks Schema derives everything from a single schema definition:
case class Person(name: String, age: Int)
object Person {
implicit val schema: Schema[Person] = Schema.derived
}
// Derive codecs for any format:
val jsonCodec = Schema[Person].derive(JsonFormat) // JSON
val avroCodec = Schema[Person].derive(AvroFormat) // Avro
val toonCodec = Schema[Person].derive(ToonFormat) // TOON (LLM-optimized)
val msgpackCodec = Schema[Person].derive(MessagePackFormat) // MessagePack
val thriftCodec = Schema[Person].derive(ThriftFormat) // Thrift
Key Featuresβ
- Universal Data Formats: JSON, Avro, TOON (compact LLM-optimized format), MessagePack, Thrift, and BSON, with Protobuf planned.
- High Performance: Register-based design stores primitives directly in byte arrays, enabling zero-allocation serialization.
- Reflective Optics: Type-safe lenses, prisms, and traversals with embedded structural metadata.
- Automatic Derivation: Derive type class instances for any type with a schema.
Installationβ
libraryDependencies += "dev.zio" %% "zio-blocks-schema" % "0.0.22"
// Optional format modules:
libraryDependencies += "dev.zio" %% "zio-blocks-schema-avro" % "0.0.22"
libraryDependencies += "dev.zio" %% "zio-blocks-schema-toon" % "0.0.22"
libraryDependencies += "dev.zio" %% "zio-blocks-schema-messagepack" % "0.0.22"
libraryDependencies += "dev.zio" %% "zio-blocks-schema-thrift" % "0.0.22"
libraryDependencies += "dev.zio" %% "zio-blocks-schema-bson" % "0.0.22"
Example: Opticsβ
import zio.blocks.schema._
case class Address(street: String, city: String)
case class Person(name: String, age: Int, address: Address)
object Person extends CompanionOptics[Person] {
implicit val schema: Schema[Person] = Schema.derived
val name: Lens[Person, String] = $(_.name)
val age: Lens[Person, Int] = $(_.age)
val streetName: Lens[Person, String] = $(_.address.street)
}
val person = Person("Alice", 30, Address("123 Main St", "Springfield"))
val updated = Person.age.replace(person, 31)
Chunkβ
A high-performance, immutable indexed sequence optimized for the patterns common in streaming, parsing, and data processing. Think of it as Vector but faster for the operations that matter most.
Why Chunk?β
Standard library collections make trade-offs that aren't ideal for streaming and binary data processing:
Vectoris general-purpose but not optimized for concatenation patternsArrayis mutable and boxes primitives when used genericallyListhas O(n) random access
Chunk is designed for:
- Fast concatenation via balanced trees (Conc-Trees)
- Zero-boxing for primitive types with specialized builders
- Efficient slicing without copying
- Seamless interop with
ByteBuffer,Array, and standard collections
Key Featuresβ
- Specialized Builders: Dedicated builders for
Byte,Int,Long,Double, etc. avoid boxing overhead. - Balanced Concatenation: Based on Conc-Trees for O(log n) concatenation while maintaining O(1) indexed access.
- Bit Operations: First-class support for bit-level operations, bit chunks backed by
Byte,Int, orLongarrays. - NonEmptyChunk: A statically-guaranteed non-empty variant for APIs that require at least one element.
- Full Scala Collection Integration: Implements
IndexedSeqfor seamless interop.
Installationβ
libraryDependencies += "dev.zio" %% "zio-blocks-chunk" % "0.0.22"
Exampleβ
import zio.blocks.chunk._
// Create chunks
val bytes = Chunk[Byte](1, 2, 3, 4, 5)
val moreBytes = Chunk.fromArray(Array[Byte](6, 7, 8))
// Efficient concatenation (O(log n))
val combined = bytes ++ moreBytes
// Zero-copy slicing
val slice = combined.slice(2, 6)
// Bit operations
val bits = bytes.asBitsByte
val masked = bits & Chunk.fill(bits.length)(true)
// NonEmptyChunk for type-safe non-emptiness
val nonEmpty = NonEmptyChunk(1, 2, 3)
val head: Int = nonEmpty.head // Always safe, no Option needed
Scopeβ
Compile-time verified resource safety for synchronous Scala code. Scope prevents resource leaks at compile time by tagging values with an unnameable type-level identityβvalues allocated in a scope can only be used within that scope. Child scope values cannot escape to parent scopes, enforced by both the abstract scope-tagged type and the Unscoped constraint on scoped.
The Problemβ
Resource management in Scala is error-prone:
// Classic try/finally - verbose and easy to get wrong
val db = openDatabase()
try {
val tx = db.beginTransaction()
try {
doWork(tx)
tx.commit()
} finally tx.close() // What if commit() throws?
} finally db.close()
// Using - better, but doesn't prevent returning resources
Using(openDatabase()) { db =>
db // Oops! Returned the resource - use after close!
}
The Solutionβ
Scope makes resource leaks a compile error, not a runtime bug:
import zio.blocks.scope._
Scope.global.scoped { scope =>
import scope._
val db: $[Database] = allocate(Resource(openDatabase()))
// Methods are hidden - can't call db.query() directly
// Must use scope.use to access:
val result = scope.use(db)(_.query("SELECT 1"))
// Trying to return `db` would be a compile error!
result // Only pure data escapes
}
// db.close() called automatically
Key Featuresβ
- Compile-Time Leak Prevention: Values of type
scope.$[A]are opaque and unique to each scope instance. Returning a scoped value from its scope is a type error. - Zero Runtime Overhead:
$[A]erases toAat runtimeβzero allocation overhead. - Structured Scopes: Child scopes nest within parents; resources clean up LIFO when scopes exit.
- Built-in Dependency Injection: Wire up your application with
Resource.from[T](wires*)for automatic constructor-based DI. - AutoCloseable Integration: Resources implementing
AutoCloseablehaveclose()registered automatically. - Unscoped Constraint: The
scopedmethod requiresUnscoped[A]evidence on the return type, ensuring only pure data (not resources or closures) can escape.
Installationβ
libraryDependencies += "dev.zio" %% "zio-blocks-scope" % "0.0.22"
Example: Basic Resource Managementβ
import zio.blocks.scope._
final class Database extends AutoCloseable {
def query(sql: String): String = s"Result: $sql"
def close(): Unit = println("Database closed")
}
Scope.global.scoped { scope =>
import scope._
// Allocate returns scope.$[Database] (scoped value)
val db: $[Database] = allocate(Resource(new Database))
// Access via scope.use - result (String) escapes, db does not
val result = scope.use(db)(_.query("SELECT * FROM users"))
println(result)
}
// Output: Result: SELECT * FROM users
// Database closed
Example: Dependency Injectionβ
import zio.blocks.scope._
case class Config(dbUrl: String)
class Database(config: Config) extends AutoCloseable { ... }
class UserRepo(db: Database) { ... }
class UserService(repo: UserRepo) extends AutoCloseable { ... }
// Resource.from auto-wires the dependency graph
// Only provide leaf values - concrete classes are auto-wired
val serviceResource: Resource[UserService] = Resource.from[UserService](
Wire(Config("jdbc:postgresql://localhost/mydb"))
)
Scope.global.scoped { scope =>
import scope._
val service = allocate(serviceResource)
scope.use(service)(_.createUser("Alice"))
}
// Cleanup runs LIFO: UserService β Database (UserRepo has no cleanup)
Example: Nested Scopes with Transactionsβ
Scope.global.scoped { connScope =>
import connScope._
val conn = allocate(Resource.fromAutoCloseable(new Connection))
// Transaction lives in child scope - cleaned up before connection
val result: String = scoped { txScope =>
import txScope._
// For-comprehension: flatMap unwraps $[Connection] to Connection,
// so c.beginTransaction() returns a raw Resource[Transaction]
for {
c <- lower(conn)
tx <- allocate(c.beginTransaction())
} yield {
tx.execute("INSERT INTO users VALUES (1, 'Alice')")
tx.commit()
"success"
}
}
// Transaction closed here, connection still open
println(result)
}
// Connection closed here
Docsβ
A zero-dependency GitHub Flavored Markdown library for parsing, rendering, and programmatic construction of Markdown documents.
Why Docs?β
Generating documentation, README files, or any Markdown content programmatically is common but error-prone with string concatenation. Docs provides:
- Type-safe AST: Build Markdown documents with compile-time guarantees
- Compile-time validation: The
md"..."interpolator validates syntax at compile time - Multiple renderers: Output to Markdown, HTML, or ANSI terminal
- Round-trip parsing: Parse Markdown to AST and render back to Markdown
Key Featuresβ
- GFM Compliant: Tables, strikethrough, autolinks, task lists, fenced code blocks
- Zero Dependencies: Only depends on zio-blocks-chunk
- Cross-Platform: Full support for JVM and Scala.js
- Type-Safe Interpolator:
md"# Hello $name"with compile-time validation - Multiple Renderers: Markdown, HTML (full document or fragment), ANSI terminal
Installationβ
libraryDependencies += "dev.zio" %% "zio-blocks-docs" % "0.0.22"
Exampleβ
import zio.blocks.docs._
// Parse Markdown
val doc = Parser.parse("# Hello\n\nThis is **bold** text.")
// Right(Doc(Chunk(Heading(H1, "Hello"), Paragraph(...))))
// Render to HTML
val html = doc.map(_.toHtml)
// Full HTML5 document with <html>, <head>, <body>
// Render to HTML fragment (just the content)
val fragment = doc.map(_.toHtmlFragment)
// "<h1>Hello</h1><p>This is <strong>bold</strong> text.</p>"
// Render to terminal with ANSI colors
val terminal = doc.map(_.toTerminal)
// Use the type-safe interpolator
val name = "World"
val greeting = md"# Hello $name"
// Doc containing: Heading(H1, Chunk(Text("Hello World")))
// Build documents programmatically
import zio.blocks.chunk.Chunk
val manual = Doc(Chunk(
Block.Heading(HeadingLevel.H1, Chunk(Inline.Text("API Reference"))),
Block.Paragraph(Chunk(
Inline.Text("See "),
Inline.Link(Chunk(Inline.Text("docs")), "/docs", None),
Inline.Text(" for details.")
))
))
// Render back to Markdown
val markdown = Renderer.render(manual)
Supported GFM Featuresβ
| Feature | Supported |
|---|---|
| Headings (ATX) | β |
| Paragraphs | β |
| Emphasis/Strong | β |
| Code (inline & fenced) | β |
| Links & Images | β |
| Lists (bullet, ordered, task) | β |
| Blockquotes | β |
| Tables | β |
| Strikethrough | β |
| Autolinks | β |
| Hard/Soft breaks | β |
| HTML (passthrough) | β |
Limitationsβ
- No frontmatter: YAML/TOML headers are not parsed
- No HTML entity decoding:
&stays as-is - No footnotes: GFM footnote extension not supported
- No emoji shortcodes:
:smile:not converted to emoji
TypeIdβ
Compile-time type identity with rich metadata. TypeId captures comprehensive information about Scala types including name, owner, type parameters, variance, parent types, and annotations.
Key Featuresβ
- Rich Metadata: Captures type name, owner, kind (class/trait/object/enum), parent types, and annotations
- Higher-Kinded Support: Works with proper types and type constructors via
AnyKind - Subtype Checking: Runtime subtype/supertype relationship checks using compile-time extracted information
- Cross-Platform: Works identically on JVM and Scala.js
Installationβ
libraryDependencies += "dev.zio" %% "zio-blocks-typeid" % "0.0.22"
Exampleβ
import zio.blocks.typeid._
// Get TypeId for any type
val listId = TypeId.of[List[Int]]
println(listId.name) // "List"
println(listId.fullName) // "scala.collection.immutable.List"
println(listId.arity) // 1 (type constructor)
// Check type relationships
trait Animal
case class Dog(name: String) extends Animal
val dogId = TypeId.of[Dog]
val animalId = TypeId.of[Animal]
dogId.isSubtypeOf(animalId) // true
// Access structural information
dogId.isCaseClass // true
dogId.isSealed // false
Contextβ
A type-indexed heterogeneous collection that stores values by their types with compile-time type safety.
Key Featuresβ
- Type-Safe Lookup: Retrieve values by type with compile-time guarantees
- Covariant:
Context[Specific]is a subtype ofContext[General] - Subtype Matching: Lookup by supertype finds matching subtypes
- Cached Access: O(1) subsequent lookups after first retrieval
Installationβ
libraryDependencies += "dev.zio" %% "zio-blocks-context" % "0.0.22"
Exampleβ
import zio.blocks.context._
case class Config(debug: Boolean)
case class Metrics(count: Int)
// Create a context with multiple values
val ctx: Context[Config & Metrics] = Context(
Config(debug = true),
Metrics(count = 42)
)
// Retrieve values by type
val config: Config = ctx.get[Config]
val metrics: Metrics = ctx.get[Metrics]
// Add or update values
val updated = ctx.update[Metrics](m => m.copy(count = m.count + 1))
// Combine contexts
val ctx1 = Context(Config(false))
val ctx2 = Context(Metrics(0))
val merged: Context[Config & Metrics] = ctx1 ++ ctx2
Streams (In Development)β
A pull-based streaming library for composable, backpressure-aware data processing.
import zio.blocks.streams._
// Coming soon: efficient pull-based streams
// that compose with any effect system
Compatibilityβ
ZIO Blocks works with any Scala stack:
| Stack | Compatible |
|---|---|
| ZIO 2.x | β |
| Cats Effect 3.x | β |
| Kyo | β |
| Ox | β |
| Akka | β |
| Plain Scala | β |
Each block has zero dependencies on effect systems. Use the blocks directly, or integrate them with your effect system of choice.
Scala & Platform Supportβ
ZIO Blocks supports Scala 2.13 and Scala 3.x with full source compatibility. Write your code once and compile it against either versionβmigrate to Scala 3 when your team is ready, not when your dependencies force you.
| Platform | Schema | Chunk | Scope | Docs | TypeId | Context | Streams |
|---|---|---|---|---|---|---|---|
| JVM | β | β | β | β | β | β | β |
| Scala.js | β | β | β | β | β | β | β |
Documentationβ
Core Schema Conceptsβ
- Schema - Core schema definitions and derivation
- Reflect - Structural reflection API
- Binding - Runtime constructors and deconstructors
- Registers - Register-based primitive storage
Optics & Navigationβ
- Optics - Lenses, prisms, and traversals
- Path Interpolator - Type-safe path construction
- DynamicValue - Schema-less dynamic values
Serializationβ
- Codec & Format - Codec, Format, BinaryCodec & TextCodec
- JSON - JSON codec and parsing
- JSON Schema - JSON Schema generation and validation
- Formats - Avro, TOON, MessagePack, BSON, Thrift
- Extension Syntax -
.toJson,.fromJson, and more
Data Operationsβ
- Patching - Serializable data transformations
- Validation - Data validation and error handling
- Schema Evolution - Migration and compatibility
Other Blocksβ
- Chunk - High-performance immutable sequences
- Scope - Compile-time safe resource management and DI
- TypeId - Type identity and metadata
- Context - Type-indexed heterogeneous collections
- Docs (Markdown) - Markdown parsing and rendering