| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Name | Name | Last commit date | ||
|---|---|---|---|---|
A Django-like Kotlin Multiplatform framework that runs on JVM (Vert.x + Virtual Threads) and Wasm (Cloudflare/Browser).
Release status: 0.6.0.0 was released on 2026-07-16, and the Maven coordinates below use this version. Production wasmWasi identity-authority hosting is not supported in this release because the combined component-host integration is not complete; use the JVM authority or another supported trusted server host for production.
Aether is designed to be a "colorless" framework that abstracts away the differences between JVM and Wasm platforms, enabling developers to write their application logic once and deploy it anywhere.
1. Add the dependency (build.gradle.kts):
kotlin {
jvm()
sourceSets {
commonMain.dependencies {
implementation("codes.yousef.aether:aether-core:0.6.0.0")
implementation("codes.yousef.aether:aether-web:0.6.0.0")
implementation("codes.yousef.aether:aether-db:0.6.0.0")
}
}
}2. Create your app (src/jvmMain/kotlin/Main.kt):
import codes.yousef.aether.core.*
import codes.yousef.aether.web.*
fun main() {
val router = router {
get("/") { exchange ->
exchange.respondHtml("<h1>Hello, Aether!</h1>")
}
get("/api/hello") { exchange ->
exchange.respondJson(mapOf("message" to "Hello, World!"))
}
}
val pipeline = pipeline {
installRecovery()
installCallLogging()
use(router.asMiddleware())
}
AetherServer.start(port = 8080, pipeline = pipeline)
}3. Run it:
./gradlew run
# Open http://localhost:8080See Full Documentation for detailed setup instructions.
aether/ ├── aether-core/ # Core abstractions (Exchange, Pipeline, Dispatcher) ├── aether-db/ # ORM and database drivers ├── aether-web/ # Routing and controllers ├── aether-browser-client/ # Typed same-origin browser HTTP, navigation, and bootstrap APIs ├── aether-ui/ # UI rendering (SSR + CBOR) ├── aether-net/ # Network transport abstraction ├── aether-ksp/ # KSP-based migration generation ├── aether-auth/ # Storage-neutral passkey-first identity engine ├── aether-auth-*/ # Optional storage, UI, OIDC, SAML, and SCIM adapters ├── aether-plugin/ # Gradle plugin ├── aether-cli/ # Command-line tools ├── example-app/ # Example application └── docs/ # Documentation and deployment guides
AETHER_IDENTITY_BOOTSTRAP_SECRET=a-development-secret-at-least-16-chars \
./gradlew :example-app:runThe example keeps the authority/storage host seam explicit. Production deployment selects either the PostgreSQL 16 or Firestore identity adapter and follows the identity deployment guide.
The framework includes comprehensive integration tests using TestContainers:
./gradlew :example-app:testThe Exchange interface represents an HTTP request-response cycle. It provides a unified API across JVM and Wasm:
exchange.respond(200, "Hello, World!")
exchange.respondJson(data = mapOf("message" to "Hello"))
exchange.respondHtml("<h1>Hello</h1>")Define routes using a type-safe DSL:
val router = Router.build {
get("/") { exchange ->
exchange.respond(200, "Home")
}
get("/users/{id}") { exchange ->
val id = exchange.attributes.get(Attributes.key<String>("id"))
// Handle user by ID
}
}Define database models using the Active Record pattern:
object Users : Model<User>() {
override val tableName = "users"
val id = integer("id", unique = true)
val username = varchar("username", maxLength = 100)
val email = varchar("email")
override fun create(row: Row): User {
return User(
id = row.getInt("id")!!,
username = row.getString("username")!!,
email = row.getString("email")!!
)
}
}
// Usage
val user = User.findById(1)
user?.username = "newname"
user?.save()Create reusable middleware for request processing:
val pipeline = Pipeline.build {
use(Recovery()) // Error handling
use(CallLogging()) // Request logging
use(ContentNegotiation()) // Content type detection
handle { exchange ->
router.handle(exchange)
}
}Render HTML using a Composable DSL:
exchange.render {
head {
title("My App")
}
body {
h1 { text("Welcome") }
div("container") {
p { text("Hello, World!") }
}
}
}Aether supports multiple database backends through pluggable drivers. Choose the backend that fits your deployment:
// JVM with Vert.x Reactive Client
val driver = VertxPgDriver.create(
host = "localhost",
port = 5432,
database = "mydb",
user = "postgres",
password = "secret"
)
DatabaseDriverRegistry.initialize(driver)Works on both JVM and Wasm platforms via PostgREST API:
val driver = SupabaseDriver.create(
projectUrl = "https://your-project.supabase.co",
apiKey = "your-anon-or-service-key"
)
DatabaseDriverRegistry.initialize(driver)
// Your models work unchanged
val users = User.objects.filter { it.active eq true }.toList()Works on both JVM and Wasm platforms via REST API:
// With API key (client-side)
val driver = FirestoreDriver.create(
projectId = "your-project-id",
apiKey = "your-api-key"
)
// With OAuth token (server-side)
val driver = FirestoreDriver.createWithToken(
projectId = "your-project-id",
accessToken = "your-oauth-token"
)
DatabaseDriverRegistry.initialize(driver)Note: Firestore is NoSQL - JOINs and LIKE queries are not supported. Use denormalized data patterns.
Uses Vert.x for HTTP server and reactive database client
Leverages Java 21 Virtual Threads for blocking I/O operations
PostgreSQL support via Vert.x Reactive PostgreSQL Client
SSR with automatic client hydration
The generic target is available for experimentation, but production wasmWasi Identity-authority hosting is not supported in 0.6.0.0: the combined Kotlin guest, WIT OpenSSL crypto host, and wasi:http integration is not complete. See the identity deployment guide.
The framework includes a pluggable network abstraction layer (:aether-net) that decouples application logic from the underlying transport mechanism. This enables:
The transport layer provides a clean interface that can be implemented for different networking paradigms while keeping application code unchanged.
This state-bag session API is for unrelated applications. Aether Identity does not use it: identity sessions are opaque, rotated credentials in the __Host-aether_session cookie and are governed by the identity session policy.
val pipeline = Pipeline().apply {
installSessions(InMemorySessionStore(), SessionConfig(
cookieName = "AETHER_SESSION",
maxAge = 3600L,
secure = true,
httpOnly = true,
sameSite = SameSite.STRICT
))
}
// In handlers
val session = exchange.session
session["user"] = "john"
val user = session["user"]
exchange.invalidateSession()The form-token helper below belongs to generic application forms. Aether Identity requires its session-bound header token plus exact Origin validation and rejects form/query-string CSRF tokens; see identity CSRF policy.
val pipeline = Pipeline().apply {
installCsrf(CsrfConfig(
tokenLength = 32,
headerName = "X-CSRF-Token"
))
}
// In templates
exchange.render {
form(action = "/submit", method = "POST") {
csrfInput(exchange) // Adds hidden CSRF token field
// form fields...
}
}The following providers belong to the generic core protocol layer. They are not Aether Identity, must not be mounted on /identity/v1, and cannot establish an identity user, organization, membership, capability, or step-up assurance. Identity applications use the passkey authority documented in Passkey-first identity.
val authConfig = AuthenticationConfig().apply {
providers["basic"] = BasicAuthProvider { credentials ->
if (validateUser(credentials.username, credentials.password)) {
AuthResult.Success(Principal.User(credentials.username, setOf("user")))
} else {
AuthResult.Failure("Invalid credentials")
}
}
providers["jwt"] = JwtAuthProvider(jwtConfig)
}
val pipeline = Pipeline().apply {
installAuthentication(authConfig) {
excludePaths.add("/public")
}
installAuthorization(AuthorizationConfig().apply {
rules["/admin"] = AuthorizationRule(requiredRoles = setOf("admin"))
})
}router {
post("/upload") { exchange ->
val file = exchange.file("document")
if (file != null) {
// file.filename, file.contentType, file.content (ByteArray)
saveFile(file)
exchange.respond(200, "Uploaded: ${file.filename}")
}
}
post("/multi-upload") { exchange ->
val files = exchange.files("attachments")
files.forEach { saveFile(it) }
exchange.respond(200, "Uploaded ${files.size} files")
}
}val wsConfig = webSocket {
path("/ws/chat") {
onConnect { session ->
broadcast("User joined")
}
onMessage { session, message ->
when (message) {
is WebSocketMessage.Text -> broadcast(message.data)
is WebSocketMessage.Binary -> session.send(message.data)
}
}
onClose { session, code, reason ->
broadcast("User left")
}
}
}
val wsServer = VertxWebSocketServer(vertx, 8080, wsConfig)
wsServer.start()Using KSP to generate migrations automatically:
@AetherModel
object Users : Model<User>() {
override val tableName = "users"
@PrimaryKey(autoIncrement = true)
val id = integer("id")
@Column(maxLength = 100)
@Index(unique = true)
val username = varchar("username", maxLength = 100)
@Column(maxLength = 255)
val email = varchar("email", maxLength = 255)
}
// Generated migration
val migration = migration("002", "Add users table") {
createTable("users") {
column("id", "SERIAL", primaryKey = true)
column("username", "VARCHAR(100)", nullable = false)
column("email", "VARCHAR(255)", nullable = false)
}
createIndex("idx_users_username", "users", listOf("username"), unique = true)
}Aether Identity deployments must follow the dedicated identity deployment guide. It is the only deployment guide in this repository that applies to the passkey authority. The old sample Docker, Compose, Nginx, SQL, and Kubernetes files used nonexistent example distribution tasks and legacy JWT/session settings; they were removed and a non-runnable tombstone remains in their place. Package unrelated aether-core applications from their own application build and deployment model.
This is a proof-of-concept framework demonstrating production-ready KMP architecture. Contributions are welcome!
MIT License - See LICENSE file for details
| Back | FazBrowse Home | New Git URL |