Skip to content

verbjs/hull

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

4 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Hull

Ecto-inspired database toolkit for Bun. Functional, type-safe, zero external dependencies.

Part of the Verb Ecosystem

Package Description
Verb Fast web framework for Bun
Hull Ecto-inspired database toolkit (this repo)
Allow Authentication library
Hoist Deployment platform

Installation

bun add @verb-js/hull

Uses Bun's built-in Bun.sql for PostgreSQL, MySQL, and SQLite.

Usage

import {
  schema,
  changeset, cast, validateRequired, validateLength,
  from, whereEq, orderBy, limit,
  connect, all, one, insert
} from "@verb-js/hull"

// Define schema (fluent builder - like Ecto)
const User = schema("users")
  .uuid("id", { primaryKey: true })
  .string("email", 255, { unique: true })
  .string("name", 100)
  .integer("age", { nullable: true })
  .boolean("active", { default: true })
  .timestamps()

const Post = schema("posts")
  .uuid("id", { primaryKey: true })
  .references("userId", "users")
  .string("title", 200)
  .text("body")
  .boolean("published", { default: false })
  .timestamps()

// Type inference
type User = InferRow<typeof User>
// => { id: string, email: string, name: string, age: number | null, ... }

// Changeset (validate + cast)
const cs = validateLength(
  validateRequired(
    cast(changeset(User, {}), params, ["email", "name", "age"]),
    ["email", "name"]
  ),
  "email", { min: 5 }
)

// Query (composable)
const query = limit(
  orderBy(
    whereEq(from(User), "active", true),
    "createdAt", "desc"
  ),
  10
)

// Execute
const repo = connect({ url: process.env.DATABASE_URL! })
const users = await all(repo, query)
const user = await one(repo, whereEq(from(User), "id", "123"))
const newUser = await insert(repo, cs)

Migrations

import { defineMigration, createTable, dropTable } from "@verb-js/hull"

export default defineMigration(
  "20241222_create_users",
  "Create users table",
  async (repo) => {
    await createTable(repo, "users", {
      id: { type: "UUID", primaryKey: true, default: "gen_random_uuid()" },
      email: { type: "VARCHAR(255)", unique: true },
      name: { type: "VARCHAR(100)" },
      createdAt: { type: "TIMESTAMPTZ", default: "now()" },
    })
  },
  async (repo) => {
    await dropTable(repo, "users")
  }
)

Comparison to Ecto

# Ecto (Elixir)
schema "users" do
  field :email, :string
  field :name, :string
  field :age, :integer
  timestamps()
end
// Hull (TypeScript)
const User = schema("users")
  .string("email", 255)
  .string("name", 100)
  .integer("age")
  .timestamps()

Database Support

Hull supports PostgreSQL and SQLite through Bun's built-in Bun.sql:

// PostgreSQL (production)
const repo = connect({ url: "postgres://user:pass@localhost:5432/myapp" })

// SQLite (local development)
const repo = connect({ url: "sqlite:///path/to/dev.db" })

The dialect is auto-detected from the connection URL. SQLite is great for local development - same schema definitions work with both databases.

Auto Migrations

Sync your schemas directly to the database without writing migration files:

import { connect, sync } from "@verb-js/hull"
import { User, Post } from "./schema"

const repo = connect({ url: process.env.DATABASE_URL! })

// Creates tables and adds missing columns automatically
await sync(repo, [User, Post])

Modules

  • schema - Define tables with fluent builder
  • changeset - Validate and cast data
  • query - Composable query builder
  • repo - Execute queries with Bun.sql
  • migration - Schema versioning + auto-sync

About

Database toolkit for Bun. Functional, type-safe, zero external dependencies.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published