Better Activity

Introduction

A fully-typed activity and audit log library for TypeScript.

better-activity is a framework-agnostic, multi-database activity / audit log library for TypeScript. You declare your entities and the actions they support once, and the SDK gives you a type-safe save() / list() / paginate() API on top of the database you already use.

It's modeled on the architecture of better-auth: a small core, adapters for every popular database, and zero magic between you and the data.

Why better-activity?

Audit logs almost always start as a one-off table glued together with a custom save_event(...) helper. As soon as you have more than a couple of event types, that helper starts collecting bugs: typos in action names, missing fields, inconsistent metadata shapes, and queries that scan the whole table.

better-activity solves that with three ideas:

  • A declared entity registry. Every entity gets a fixed set of allowed actions and an optional metadata type. Misuses are caught at compile time.
  • One core, many backends. Postgres, MySQL, SQLite, MongoDB, Drizzle, Prisma, Kysely, and an in-memory adapter for tests — all behind the same API.
  • Boring, indexed schema. The activity table is intentionally narrow and ships with the indexes you actually need.

Features

A 30-second taste

activity.ts
import { betterActivity } from "better-activity";
import { postgresAdapter } from "better-activity/adapters/postgres";
import { Pool } from "pg";

export const activity = betterActivity({
  database: postgresAdapter({ pool: new Pool() }),
  entities: {
    user: {
      actions: ["created", "updated", "logged_in", "logged_out"],
      metadata: {} as { ip?: string; userAgent?: string },
    },
    project: {
      actions: ["created", "archived", "member_added"],
    },
  },
});

await activity.save({
  entity: "user",
  entityId: "usr_123",
  action: "logged_in",
  actorId: "usr_123",
  metadata: { ip: "1.2.3.4" },
});

const events = await activity.list({
  entity: "project",
  entityId: "prj_456",
  limit: 50,
});

Next steps

On this page