A thin, type-safe SQL toolkit for MoonBit
Dependencies
@aya.from(User::table())
|> @aya.Query::filter(u => u.age.gte(18) & u.deleted_at.is_none())
|> @aya.Query::map(u => @aya.sel(u.name))
|> @aya.Query::order_by(u => [u.name.asc()])
|> @aya.Query::limit(20)SELECT u."name"
FROM "users" AS u
WHERE u."age" >= ? AND u."deleted_at" IS NULL
ORDER BY u."name" ASC
LIMIT ?
-- parameters: [18, 20]moon add Allianaab2m/aya// moon.pkg
import {
"Allianaab2m/aya",
"Allianaab2m/aya/driver/sqlite",
}#ayatable(name="users", alias="u")
pub(all) struct User {
#ayaid
id : Int
name : String
age : Int
deleted_at : String?
} derive(Debug, Eq)aya-kit codegenasync fn main {
@sqlite.with_connection("app.db", driver => {
let db = @aya.Tx::new(driver)
let adults = (@aya.from(User::table())
|> @aya.Query::filter(u => u.age.gte(18))).run(db)
println(adults.length())
})
}classDiagram
class Table~Cols,R~ {
cols : Cols
all : Selection~R~
write : Binding~R~
}
class Selection~Out~ {
exprs : Array~RawExpr~
read : Row to Out
}
class Binding~In~ {
columns : Array~String~
write : In to values
}
class Query~Cols,A~ {
cols : Cols
projection / wheres / group / order
decode : Row to A
}
class Reducer~Out~ {
aggregates only
}
class Column~T~ {
tbl : String
name : String
}
class Expr~T~ {
raw : RawExpr
}
class Tx~D~ {
db : D
depth : Int
}
class RawExpr {
Col / Lit / Bin
Unary / InList / Agg
}
Table --> Selection : all
Table --> Binding : write
Table --> Query : from
Query --> Reducer : reduce / group_by
Column --> Expr : expr
Expr --> RawExpr : raw
Selection --> RawExpr : exprs
Tx --> Query : run / one / first| Type | Means | Chapter |
|---|---|---|
| Table[Cols, R] | a table, plus how to read and write one row of it | Table |
| Selection[Out] | which columns to read, and how to decode them | Table |
| Binding[In] | which columns to write, and how to encode them | Table |
| Column[T] / Expr[T] | a typed column reference / a typed expression | Query |
| Query[Cols, A] | a SELECT under construction, yielding A | Query |
| Insert / Update[Cols] / Delete[Cols] | a write under construction | DML |
| Nullable[C, R] | the right-hand side of an outer join | JOIN |
| Reducer[Out] | a summary over many rows — aggregates only | Aggregation |
| Executor / Driver / Tx[D] | run a statement / bracket a transaction | Execution |
| 1. Table | defining tables, codegen, and the row-vs-domain seam |
| 2. Query | Query and the expression language |
| 3. DML | Insert, Update, Delete |
| 4. JOIN | inner and outer joins, and naming the joined shape |
| 5. Aggregation | Reducer, reduce, group_by |
| 6. Execution | Executor, Driver, Tx, transactions, drivers |
| 7. Repository | the pattern aya is designed to sit under |
| 8. Schema and migrations | DDL from the same annotations, and aya-kit |
| 9. Design notes | why the types are shaped this way, and what is missing |
src/*.mbt core library — expressions, projection, query, DML, emission
src/gen/ entity parsing — attributes to IR — and column-handle emission
src/ddl/ IR to snapshot, diff, and DDL
src/kit/ config and migration planning, all of it pure
src/kit/aya-kit/ the CLI (aya-kit)
src/driver/sqlite/ SQLite driver
src/driver/postgres/PostgreSQL driver
src/driver/fake/ recording fake, for testing repositories
src/example/ two worked entities: a plain one and a row-vs-domain one
migrations/ the migrations generated from src/examplemoon check # type check
moon test # tests
moon fmt # format
moon info # refresh .mbtipub(open) trait SqlNum {
}pub(open) trait SqlOrd {
}pub(all) suberror DbError {
ConnectionFailed(String)
QueryFailed(sql~ : String, message~ : String)
NotFound(sql~ : String)
TooManyRows(sql~ : String, got~ : Int)
RollbackOnly(cause~ : Error)
} derive(Debug)pub(all) suberror DecodeError {
MissingColumn(String)
TypeMismatch(column~ : String, expected~ : String)
Malformed(String)
} derive(Debug)pub(all) suberror StatementError {
EmptyInsert(table~ : String)
ArityMismatch(table~ : String, expected~ : Int, got~ : Int)
EmptyUpdate(table~ : String)
DuplicateAlias(tbl~ : String)
} derive(Debug)pub struct Column[T] {
tbl : String
name : String
}type Nullable[C, R]type Reducer[Out]pub struct Selection[Out] {
exprs : Array[RawExpr]
read : (ArrayView[SqlValue]) -> Out raise DecodeError
}pub struct Tx[D] {
db : D
depth : Int
failure : Error?
}let db = @aya.Tx::new(driver)
let tickets = SqlTickets::new(db)
let users = SqlUsers::new(db)
@aya.transaction(db, _ => {
tickets.save(a) // both saves land in one transaction
users.save(b)
})fn[A, B, R] split2(f : (A, B) -> R) -> (((A, B)) -> R)A thin, type-safe SQL toolkit for MoonBit
Dependencies