Skip to content

Releases: shaferllc/keel

v0.36.0 — notifications

Choose a tag to compare

@tshafer tshafer released this 11 Jul 01:02

Send a message to one or many recipients over pluggable channels: notify(user, new InvoicePaid(4200)). A Notification declares via() (channels) and per-channel content (toMail, toArray).

Built-in channels: MailChannel (via the mailer, routed by email or routeNotificationFor), DatabaseChannel (inserts toArray into a table), and ArrayChannel (for tests). Set shouldQueue = true to deliver from a queued job — this is where the mail and queue layers compose. A custom channel is one send method. New generator keel make:notification.

See docs/notifications.md.

v0.35.0 — queues & jobs

Choose a tag to compare

@tshafer tshafer released this 11 Jul 00:49

Move slow work off the request path: dispatch(new SendWelcome(id)) places a Job (or a plain function) on a queue, and a pluggable QueueDriver decides when it runs. Built-in drivers: SyncDriver (runs immediately — the default), MemoryDriver (defers; work() drains FIFO, inspect .jobs). dispatch takes { delay, queue }; a push-only custom driver is the seam for a real broker (e.g. Cloudflare Queues). New generator keel make:job. Core imports no broker, edge-safe.

See docs/queues.md.

v0.34.0 — mail

Choose a tag to compare

@tshafer tshafer released this 11 Jul 00:49

A fluent, edge-safe mailer: mail().to().subject().html().send() over a pluggable Transport (like the database Connection). setMailer(transport, { from }) registers the default. Built-in transports: ArrayTransport (collects to .sent, the default and ideal for tests), LogTransport (logs instead of delivering), and fetchTransport({ url, headers, body }) for provider HTTP APIs (Resend/Postmark/Mailgun) over fetch — the core imports no SDK. send() validates recipient/subject/body/from.

See docs/mail.md.

v0.33.0 — model casts + mass-assignment guarding

Choose a tag to compare

@tshafer tshafer released this 11 Jul 00:49

Attribute casts: static casts = { active: "boolean", meta: "json", joined_at: "date" } round-trips columns as real JS types — cast on read, back to storable primitives on write, so boolean/json bind cleanly on drivers that reject JS booleans/objects. Types: int, float, boolean, string, json/array, date.

Mass-assignment guarding: static fillable (allowlist) / static guarded (denylist) filter what create() and fill() accept; forceFill() bypasses. With neither declared, behavior is unchanged (backward compatible).

See docs/models.md#attribute-casts.

v0.32.0 — factories & seeders

Choose a tag to compare

@tshafer tshafer released this 11 Jul 00:49

factory(Model, (f, i) => ({ ... })) builds model attributes with a built-in, dependency-free Faker (seedable for deterministic runs). .make() (unsaved) / .create() (persisted), .count(n) for batches, inline overrides. Seeder classes with run() + call([...]) composition; seed(DatabaseSeeder) runs one. New generators keel make:factory and keel make:seeder. Edge-safe (no external faker library).

See docs/factories.md.

v0.31.0 — model relationships

Choose a tag to compare

@tshafer tshafer released this 11 Jul 00:49

Model relationships over the query builder: hasMany / hasOne / belongsTo / belongsToMany, with conventional foreign keys you can override. Relations are awaitable and expose .query(); Model.load(models, "posts") eager-loads with one whereIn per relation (fixes N+1). belongsToMany reads a pivot and offers attach / detach / sync. Loaded relations stay out of save() and serialize via toJSON(). No JOINs, edge-safe.

See docs/models.md#relationships.

v0.30.0 — migrations

Choose a tag to compare

@tshafer tshafer released this 10 Jul 21:32

Version your schema with a fluent builder + migrator:

const migrations: Migration[] = [{
  name: "01_create_users",
  up: (s) => s.createTable("users", (t) => {
    t.id();
    t.string("email").unique();
    t.boolean("active").default(true);
    t.timestamps();
  }),
  down: (s) => s.dropTable("users"),
}];

await new Migrator(connection, "postgres").up(migrations);   // idempotent
await new Migrator(connection, "postgres").down(migrations); // roll back last batch

Applied migrations are batch-tracked in a migrations table; SQL is dialect-aware (sqlite/mysql/postgres). See docs/migrations.md.

v0.29.0 — active-record models

Choose a tag to compare

@tshafer tshafer released this 10 Jul 21:24

A tiny active-record Model over the query builder:

class User extends Model {
  static table = "users";
  declare id: number;
  declare email: string;
}

const user = await User.find(1);              // User | null
const created = await User.create({ email });
user.email = "new@x.com";
await user.save();                             // insert or update
await user.delete();

Static find/findOrFail/all/first/where/create; instance save/delete/fill/toJSON. Model.query() drops to the raw builder. Runs on any registered connection — edge-safe. See docs/models.md.

v0.28.0 — database query builder

Choose a tag to compare

@tshafer tshafer released this 10 Jul 21:20

A driver-agnostic query builder — the foundation of Keel's data layer:

setConnection(myConnection, "postgres");   // any driver, two methods

await db("users").where("active", true).orderBy("name").get();
await db("users").where("id", 1).first();
await db("posts").whereIn("id", [1, 2, 3]).count();
const id = await db("users").insertGetId({ email, name });

Parameterized SQL (injection-safe) through a two-method Connection — works with D1, Neon/Postgres, PlanetScale, Turso, better-sqlite3, and pg. The core imports no driver, so it stays edge-safe. An active-record Model layer + migrations build on this next. See docs/database.md.

v0.27.0 — authentication

Choose a tag to compare

@tshafer tshafer released this 10 Jul 21:16

Session-based auth, tying together the session + hash primitives:

setUserProvider((id) => db.users.find(id));  // once, in a provider

if (user && await hash.verify(user.password, password)) {
  auth().login(user.id);
}
auth().check();  auth().id();  await auth().user();  auth().logout();

Protect routes with the authGuard({ redirectTo? }) middleware (401 for APIs, redirect for web). See docs/authentication.md.