Skip to main content
Introducing packages.sweber.dev
Documentation menuMySQL and MariaDB

MySQL and MariaDB

Store events in MySQL 8 or MariaDB 10.6+ with mysql2, mariadb or any driver.

import { createAuditLog } from "@sweberdev/logarithm"
import { migrateMysql, mysqlStore } from "@sweberdev/logarithm/mysql"
import mysql from "mysql2/promise"

const pool = mysql.createPool(process.env.DATABASE_URL!)
await migrateMysql({ client: pool })
export const audit = createAuditLog({ store: mysqlStore({ client: pool }) })

client is anything with a query(sql, values) method: a mysql2/promise pool or connection, a mariadb pool or connection, or a small wrapper around your driver that resolves to { rows } (and { affectedRows } for deletes). PlanetScale and other MySQL-compatible services work the same way.

Migrations

migrateMysql runs CREATE TABLE IF NOT EXISTS with the indexes. For Drizzle, Prisma, Flyway or Liquibase, print the SQL once and add it to a migration:

console.log(mysqlSchema({ table: "audit_events", database: "audit" }))

Table

ColumnTypeContent
idVARCHAR(64)ULID, sorts by creation time
occurred_atDATETIME(3)When it happened, in UTC
tenant_idVARCHAR(191)Customer organisation, or NULL
actionVARCHAR(200)e.g. project.updated
actor_idVARCHAR(191)Copy of actor.id for fast filters
actor, targets, changes, context, metadataJSONEvent data
searchTEXTLower-case text for search

The table uses utf8mb4_bin, so ids, actions and tenants compare exactly, as in Postgres and SQLite. Timestamps are written and read as UTC text, so the connection's time zone and the driver's timezone option do not matter.

Transactions

Pass a connection inside a transaction so the event commits or rolls back together with the change:

const conn = await pool.getConnection()
try {
  await conn.beginTransaction()
  await conn.query("UPDATE projects SET name = ? WHERE id = ?", [name, id])
  await createAuditLog({ store: mysqlStore({ client: conn }) }).record({ ... })
  await conn.commit()
} catch (error) {
  await conn.rollback()
  throw error
} finally {
  conn.release()
}

Protect the table

Grant your application user only INSERT and SELECT on the table and run retention with a separate user. Logarithm Pro adds a hash chain that makes changes visible even to someone with full database access.