I started writing adze because I didn’t want anyone’s first experience with Hew to be “cool language, no libraries.” If you can’t connect to Postgres, people move on. So before v0.1.0 was even tagged, I was sketching out what a package manager for Hew would look like. (The name comes from the woodworking tool. It’s short, easy to type, and wasn’t taken on any registry I could find.)
Three Tiers of Packages
The namespace system fell into place early:
std::*— bundled with the compiler, always available.std::io,std::encoding::json. You never install these.hew::*— official ecosystem packages, maintained by the project. Install them withadze install hew::db::postgres. These are the day-1 libraries that make the language usable for real work.{org}::*or{user}::*— community packages. Anyone can publish under their own namespace once they’ve registered.
The hew:: prefix is reserved — the registry enforces this at publish time. If you’re not a trusted publisher, the server returns a 403: The "hew" namespace is reserved and cannot be published to. I wanted something like Rust’s ecosystem but with a clearer ownership signal — when you see hew:: you know it’s official.
The Library Pattern
Every ecosystem library follows the same structure, and this is probably the part I’m most opinionated about:
db/postgres/
├── hew.toml # package manifest
├── postgres.hew # public API — actors, types, traits
├── Cargo.toml # Rust build for FFI
└── src/lib.rs # Rust FFI implementationThe philosophy: hoist as much logic as possible into the .hew layer. Rust FFI should only handle actual C-level operations — socket management, system calls, wire protocols. Everything else — connection management, result accessors, parameterized queries — belongs in Hew.
Here’s the actual hew.toml for the Postgres package:
[package]
name = "hew::db::postgres"
version = "0.1.0"
description = "PostgreSQL client for Hew — actor-based connection with parameterized queries"
authors = ["Hew Contributors"]
license = "MIT OR Apache-2.0"
keywords = ["database", "postgres", "sql"]
repository = "https://github.com/hew-lang/ecosystem"Deliberately minimal — no [build] section, [features] matrix, or workspace inheritance.
And the postgres actor itself — the real one from postgres.hew — shows how naturally the actor model maps to database connections:
pub actor Conn {
var handle: i64 = 0;
init(connstr: String) {
handle = hew_pg_connect(connstr);
}
receive fn query(sql: String) -> Result {
hew_pg_query(handle, sql)
}
receive fn execute(sql: String) -> i64 {
hew_pg_execute(handle, sql)
}
receive fn close() {
hew_pg_close(handle);
}
}A database connection is a stateful thing that processes requests one at a time — you spawn it, send it queries, and it manages its own lifecycle, with serialized access coming free from the actor model. Want pooling? Spawn ten of them behind a router actor.
The FFI boundary is explicit — extern "C" declarations at the bottom of the .hew file, Rust implementations in src/lib.rs. The Result type gets accessor methods through a trait:
trait ResultMethods {
fn rows(self: Result) -> i32;
fn cols(self: Result) -> i32;
fn get(self: Result, row: i32, col: i32) -> String;
fn free(self: Result);
}Day-1 Packages
The ecosystem repo ships six packages — the ones I think you need to build something real on day one:
hew::db::postgres— PostgreSQL with parameterized querieshew::db::redis— Redis clienthew::db::sqlite— embedded SQLite, because sometimes you just need a filehew::misc::glob— file globbing (sounds trivial until you need it at 2am)hew::image::magick— ImageMagick bindings with thumbnail integration testshew::math::stats— statistical functions, distributions, 27 tests
Each wraps battle-tested Rust crates — tokio-postgres, redis-rs, rusqlite — so we’re not reimplementing wire protocols. The initial commit on February 22 added postgres, redis, sqlite, and glob. A day later I rewrote all of them to use the actor pattern properly, added magick and stats, then spent an embarrassing amount of time fixing stats.hew because I’d used struct instead of type and [] instead of Vec::new(). (The compiler caught both. Eventually.)
The Registry
The registry runs on Cloudflare Workers — a pragmatic choice. I needed something that could handle package metadata lookups with low latency globally, didn’t want to run a server, and Workers’ KV store (a global key-value database built into the platform) maps well to the “look up package by name and version” access pattern.
Authentication uses GitHub’s device flow — adze login gives you a code, you enter it on github.com, the registry gets an OAuth token. Four commits tell the whole story: initial worker and mirror server, then fixing Ed25519 (a public-key signature scheme) counter-signing on Workers (the Web Crypto API rejects JWKs with an alg field for Ed25519, which took longer to figure out than I’d like to admit), then enforcing reserved prefixes at publish time, then adding trusted publishers so the hew:: namespace can have multiple authorized contributors.
Every published package is signed with Ed25519. When you run adze publish, your local key signs the checksum, and the registry verifies the signature against your registered public key before accepting the tarball. The registry then counter-signs with its own key so clients can verify packages came through the official registry. There’s no --skip-verify flag. (I thought about adding one. Then I thought about it more and didn’t.)
The verify flow is four checks, and all four must pass:
- Signature and key fingerprint are present
- The fingerprint matches a registered public key
- The registered key belongs to the authenticated user
- The Ed25519 signature over the checksum is cryptographically valid
Fail any one and you get a specific error message telling you what to do: Missing signature or key_fingerprint. Sign the package with: adze key generate. I wanted error messages that tell you the next command to run, not just what went wrong.
The dependency resolver is basic — it handles version constraints (>=, <, ranges) but hasn’t been tested with deep dependency trees. The manifest format doesn’t have a [dependencies] section yet, which means ecosystem packages can’t depend on each other. That’s fine for six independent libraries but won’t last. And when adze install pulls a package with FFI, the user needs cargo on their PATH. Shipping precompiled binaries for every platform would solve this, but that’s a distribution problem I’m not ready for.