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, DB2, or SQLite
- 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 |
| SQLite | 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();
}
static public Bijection<CompanyId, String> bijection =
Bijection.of(CompanyId::value, CompanyId::new);
static public PgType<CompanyId> pgType =
PgTypes.text.to(Bijection.of(CompanyId::new, CompanyId::value));
static public 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.