Skip to content

Repository files navigation

Semola

Semola

Zero-dependency TypeScript utilities for modern Bun apps

Type-safe APIs, Redis queues, pub/sub, i18n, caching & auth with tree-shakeable imports

Tests npm version Bun TypeScript License

✨ Features

Module Description Import
🚀 API Framework Type-safe REST API with OpenAPI & Bun-native routing semola/api
📬 Queue Redis-backed job queue with timeouts & concurrency semola/queue
📡 PubSub Type-safe Redis pub/sub for real-time messaging semola/pubsub
🔐 Policy Policy-based authorization with type-safe guards semola/policy
🌍 i18n Compile-time validated internationalization semola/i18n
💾 Cache Redis cache wrapper with TTL & automatic serialization semola/cache
⏰ Cron In-memory and OS cron scheduler for periodic task execution semola/cron
🔁 Workflow Durable resumable workflows with retries and hooks semola/workflow
⚠️ Errors Result-based error handling without try/catch semola/errors
📃 Logging A simple logging utility semola/logging
⌨️ Prompts Interactive zero-dependency CLI prompts semola/prompts
🖥️ CLI Non-interactive CLI builder with schema validation semola/cli
🗄️ ORM Type-safe data layer with query APIs semola/orm
💡 Extra A collection of tiny utilities semola/extra

🚀 Quick Start

# With Bun (recommended)
bun add semola

# With npm
npm install semola

Build a Type-Safe API

import { Api } from "semola/api";
import { z } from "zod";

const api = new Api();

api.defineRoute({
  path: "/hello/:name",
  method: "GET",
  request: {
    params: z.object({ name: z.string() }),
  },
  response: {
    200: z.object({ message: z.string() }),
  },
  handler: async (ctx) => {
    return ctx.json(200, { message: `Hello, ${ctx.params.name}!` });
  },
});

api.listen(3000);
console.log("Server running on http://localhost:3000");

Handle Errors Without Try-Catch

import { mightThrow } from "semola/errors";

const [error, data] = await mightThrow(fetch("https://api.example.com"));

if (error) {
  console.error("Request failed:", error);
  return;
}

console.log("Success:", data);

mightThrow and mightThrowSync default their error type to Error. If a promise or function can reject or throw non-Error values, pass a custom error generic.

const [customError] = await mightThrow<never, { code: string }>(
  Promise.reject({ code: "RATE_LIMITED" }),
);

Process Background Jobs

import { Queue } from "semola/queue";

const queue = new Queue({
  name: "emails",
  redis: redisClient,
  handler: async (data) => {
    await sendEmail(data);
  },
});

await queue.enqueue({ to: "user@example.com", subject: "Hello" });

Send Real-Time Messages

import { PubSub } from "semola/pubsub";

const pubsub = new PubSub({
  subscriber: redisClient,
  publisher: redisClient,
  channel: "notifications",
});

// Subscribe to messages
const unsubscribe = await pubsub.subscribe((message) => {
  console.log("Received:", message);
});

// Publish a message
await pubsub.publish({ userId: 123, text: "New alert!" });

await unsubscribe();

Cache Data with TTL

import { Cache } from "semola/cache";

const cache = new Cache({
  redis: redisClient,
  ttl: 3600000, // 1 hour in milliseconds
});

// Store data
await cache.set("user:123", { name: "John", age: 30 });

// Retrieve data
const user = await cache.get("user:123");
console.log(user);

Schedule Recurring Tasks

import { Cron } from "semola/cron";

const cleanup = new Cron({
  name: "daily-cleanup",
  schedule: "@daily",
  handler: async () => {
    await deleteOldLogs();
    await archiveInactiveUsers();
  }
});

cleanup.run();

Query a Database

import { createOrm, defineTable, json, string, uuid } from "semola/orm";

const users = defineTable("users", {
  id: uuid("id").primaryKey().notNull(),
  name: string("name").notNull(),
  email: string("email").unique().notNull(),
  metadata: json<{ plan: string }>("metadata"),
});

const db = createOrm({
  adapter: "sqlite",
  url: ":memory:",
  tables: { users },
});

const rows = await db.users.findMany({
  where: { name: { contains: "John" } },
  take: 10,
});

const user = await db.users.create({
  data: {
    id: "1",
    name: "John Doe",
    email: "john@example.com",
  },
});

console.log(rows, user);

Check Permissions

import { Policy, eq, has } from "semola/policy";

type User = { id: number; role: string; permissions: string[] };

const policy = new Policy<User>();

// Allow admins full access
policy.allow({
  action: ["create", "update", "delete"],
  conditions: { role: eq("admin") },
  reason: "Admins have full access",
});

// Allow users with a specific permission
policy.allow({
  action: "read",
  conditions: { permissions: has("posts:read") },
});

// Check if user can perform an action
const user: User = { id: 1, role: "admin", permissions: [] };
const result = policy.can("update", user);
console.log(result.allowed); // true

Internationalize Your App

import { I18n } from "semola/i18n";

const i18n = new I18n({
  defaultLocale: "en",
  locales: {
    en: { greeting: "Hello, {name:string}!" },
    es: { greeting: "¡Hola, {name:string}!" },
  },
});

console.log(i18n.translate("greeting", { name: "World" }));

Log your messages

import { ConsoleProvider, Logger } from "semola/logging";

const logger = new Logger("database", [new ConsoleProvider()]);
logger.info("Hello!");

Retry a function multiple times

import { createRetry } from "semola/extra";

type FriendRequest = {
  from: string;
  to: string;
};

async function sendFriendRequest(req: FriendRequest) {
  console.log(`sending from ${req.from} to ${req.to}`);
  await sender(req);
}

const callable = createRetry(
  async () => {
    await sendFriendRequest({ from: "user1@gmail.com", to: "user2@gmail.com" });
  },
  {
    maxRetries: 3,
    onFailedAttempt: () => {
      console.log(`Resending the request`);
    },
    onError: ({ error }) => {
      console.error(error.message);
    },
  },
);

await callable();

📦 Installation

# Install core package
bun add semola

# Optional: Install validation library (Zod, Valibot, ArkType)
bun add zod

🔥 Why Semola?

Semola (pronounced "seh-MOH-lah") is the batteries-included toolkit TypeScript developers have been waiting for.

Stop piecing together half-baked solutions from npm. Stop wrestling with type definitions that lie to you. Semola gives you everything you need to build production-ready Bun applications with confidence: type-safe APIs, background job queues, real-time messaging, caching, authorization, and error handling. All working together seamlessly out of the box.

API Framework Comparison

Semola Express Fastify Hono Elysia
Bun Native ⚠️
Zero Dependencies
Type-Safe Routes ⚠️
Auto OpenAPI ⚠️ ⚠️ ⚠️
Tree-Shakeable
Standard Schema ⚠️

Performance Benchmarks

Semola API is the fastest API framework for Bun.

Framework Avg Req/Sec Latency Avg (ms) vs Semola
Semola 40,050 1.88 baseline
Elysia 37,185 2.13 1.1x slower
Hono 34,611 2.31 1.2x slower
Fastify 26,330 3.70 1.5x slower
Express 20,031 5.02 2x slower
NestJS 16,118 6.21 2.5x slower

Higher is better for req/sec, lower is better for latency.

What Makes Semola Different

  • 🎯 Bun-first: Engineered specifically for Bun's performance. No Node.js baggage.
  • 🧩 Modular by design: Import only what you need. Your bundle stays lean.
  • 🔒 Type safety that actually works: From request validation to response serialization, TypeScript catches errors before they hit production.
  • 📄 Documentation writes itself: Auto-generated OpenAPI specs from your code. No more stale docs.
  • 🚫 Error handling reimagined: No more try-catch spaghetti. Clean result tuples that compose beautifully.
  • Schema validation freedom: Use Zod, Valibot, ArkType, or any Standard Schema library. Your choice.
  • 🔋 Batteries included: Everything you need in one cohesive toolkit. No 50 dependencies to audit.

📖 Documentation

  • API Framework - Type-safe REST API framework with OpenAPI
  • Queue - Redis-backed job queue with timeouts & concurrency
  • PubSub - Type-safe Redis pub/sub
  • Cron - In-memory and OS cron scheduler for periodic task execution
  • Workflow - Durable and resumable workflows with retries and hooks
  • Policy - Policy-based authorization
  • i18n - Type-safe internationalization
  • Cache - Redis cache wrapper with TTL
  • Errors - Result-based error handling
  • Logging - Logging utility
  • Prompts - Interactive CLI prompts
  • CLI - Non-interactive CLI builder
  • ORM - Type-safe data layer with SQLite support
  • Extra - A collection of tiny utilities

🛠️ Development

# Install dependencies
bun install

# Run tests
bun test

# Build package
bun run build

# Lint & typecheck
bun check

📝 Publishing

This package uses GitHub Actions for automated publishing. To release:

  1. Bump version: bun pm version <major|minor|patch>
  2. Create a GitHub release with a new tag (e.g., v0.4.0)
  3. The GitHub Action automatically publishes to npm with provenance

About

⚡ Zero-dependency TypeScript utilities for modern Bun apps. Type-safe APIs, Redis queues, pub/sub, i18n, caching, auth policies and more

Topics

Resources

Stars

9 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages