Introduction to Typr DB
Your database schema is a contract. Typr DB enforces it.
Typr DB generates type-safe code from your database schema—row types, ID wrappers, repositories, and a SQL DSL—for Java, Kotlin, and Scala. Six databases supported. One type system.
The Problem: The Database Boundary​
The compiler checks your application code. But at the database boundary, you're on your own:
- Column names matched by strings that can silently break
- ID types mixed up (
UserIdvsOrderIdvsLong) - Nullable columns accessed without null checks
- Schema changes that break code at runtime, not compile time
- New team members who don't know which types go where
The result: Bugs that compile, ship, and break in production.
The Solution: Schema as Contract​
Typr DB treats your database schema as the source of truth:
- Read your schema from PostgreSQL, MariaDB, Oracle, SQL Server, DuckDB, or DB2
- Generate typed code for every table, view, and relationship
- Enforce at compile time that all database access uses the correct types
When you change a column, rename a table, or modify a relationship—the compiler shows every impact. No grep. No prayer. Just fix the compile errors.
Who Benefits​
New team members are productive on day one. The types guide them to correct usage. The compiler catches their mistakes before code review.
Contractors work within well-defined boundaries. They can't accidentally use the wrong ID type or forget a nullable check. The contract is explicit.
Seniors review schema changes—10 lines of migration—not 1000 lines of implementation. High-leverage review where it matters.
What Gets Generated​
| Component | Description |
|---|---|
| Row types | Immutable data classes matching your table structure |
| ID types | UserId, OrderId—distinct types that can't be mixed up |
| Repositories | Type-safe CRUD operations for every table |
| SQL DSL | Compose queries with full type checking |
| SQL file bindings | Write .sql files, get typed methods |
| Test infrastructure | Mock repositories, test data generators |
Supported Databases​
| Database | Status |
|---|---|
| PostgreSQL | Full support |
| MariaDB / MySQL | Full support |
| Oracle | Full support |
| SQL Server | Full support |
| DuckDB | Full support |
| IBM DB2 | Full support |
Supported Languages​
| Language | Features |
|---|---|
| Java 17+ | Records, sealed interfaces, modern idioms |
| Kotlin | Value classes, null safety, data classes |
| Scala 2.13 / 3.x | Case classes, Option types, functional style |
Example: What You Get​
From a simple table, Typr generates everything you need:
/** Type for the primary key of table `showcase.company` */
public record CompanyId(String value) {
public CompanyId withValue(String value) {
return new CompanyId(value);
}
@Override
public java.lang.String toString() {
return value.toString();
}
public static Bijection<CompanyId, String> bijection =
Bijection.of(CompanyId::value, CompanyId::new);
public static PgType<CompanyId> pgType =
PgTypes.text.to(Bijection.of(CompanyId::new, CompanyId::value));
public static PgType<List<CompanyId>> pgTypeArray = pgType.array();
}
/** Table: showcase.company Primary key: id */
public record CompanyRow(
CompanyId id,
String name,
Optional<Integer> foundedYear,
/** Default: true */
Optional<Boolean> active,
/** Default: CURRENT_TIMESTAMP */
Optional<LocalDateTime> createdAt)
implements Tuple5<
CompanyId, String, Optional<Integer>, Optional<Boolean>, Optional<LocalDateTime>> {
public interface CompanyRepo {
DeleteBuilder<CompanyFields, CompanyRow> delete();
Boolean deleteById(CompanyId id, Connection c);
Integer deleteByIds(List<CompanyId> ids, Connection c);
CompanyRow insert(CompanyRow unsaved, Connection c);
CompanyRow insert(CompanyRowUnsaved unsaved, Connection c);
Long insertStreaming(Iterator<CompanyRow> unsaved, Integer batchSize, Connection c);
/** NOTE: this functionality requires PostgreSQL 16 or later! */
Long insertUnsavedStreaming(Iterator<CompanyRowUnsaved> unsaved, Integer batchSize, Connection c);
SelectBuilder<CompanyFields, CompanyRow> select();
List<CompanyRow> selectAll(ConnectionRead c);
Optional<CompanyRow> selectById(CompanyId id, ConnectionRead c);
List<CompanyRow> selectByIds(List<CompanyId> ids, ConnectionRead c);
Map<CompanyId, CompanyRow> selectByIdsTracked(List<CompanyId> ids, ConnectionRead c);
UpdateBuilder<CompanyFields, CompanyRow> update();
Boolean update(CompanyRow row, Connection c);
CompanyRow upsert(CompanyRow unsaved, Connection c);
List<CompanyRow> upsertBatch(Iterator<CompanyRow> unsaved, Connection c);
/** NOTE: this functionality is not safe if you use auto-commit mode! it runs 3 SQL statements */
Integer upsertStreaming(Iterator<CompanyRow> unsaved, Integer batchSize, Connection c);
}
Video Demo​
Write your SQL in .sql files. Typr regenerates correct mapping code on save.
Types of Database Interactions​
- CRUD Operations: Repository methods for simple and safe CRUD, plus the SQL DSL for batch operations.
- Simple Reads: Joins and filters using the SQL DSL.
- Complex Reads: Aggregations, window functions, CTEs—handled by writing SQL files.
- Dynamic Queries: For truly dynamic queries, Typr integrates with your existing database library.
Ready to make your database boundary type-safe? Keep reading to discover what Typr DB can do.