Skip to content
Tikab's Toolkit

Audit

The answer to "who did what, when". There are no Django-style signals on this stack — mutations call recordAudit() explicitly. That is a feature: the audit trail is visible in the code path that causes it, not hidden in a receiver three files away.

The write side

Live from the source:

export async function recordAudit(args: {
  actorUserId: string | null;
  actorName: string | null;
  action: string;
  entityType: string;
  entityId?: number | null;
  projectId?: number | null;
  summary: string;
  meta?: Record<string, unknown> | null;
}): Promise<void> {
  try {
    await db.insert(auditLog).values({
      actorUserId: args.actorUserId,
      actorName: args.actorName,
      action: args.action,
      entityType: args.entityType,
      entityId: args.entityId ?? null,
      projectId: args.projectId ?? null,
      summary: args.summary,
      meta: args.meta ?? null,
    });
  } catch {
    // Best-effort: never let history-keeping fail real work.
  }
}

Design decisions baked in:

  • History outlives its subjects. actorName is denormalized (survives user deletion); projectId is a plain integer scope with no foreign key (survives deletion of the thing it scopes to). Deleting a project does not erase the history of what happened in it.
  • Best-effort by contract. A failed history write must never fail the real work — the catch swallows, deliberately.

The read side

listAudit({ projectId?, limit? }) returns wire-friendly rows with a live-name fallback (current user name when the account still exists, the denormalized snapshot otherwise). The example renders it project-scoped for project admins and globally in /admin/audit.

Conventions

  • Action keys are dotted: task.create, task.status, admin.users.deactivate, workspace.create — stable, greppable, filterable.
  • Summaries are for humans. One readable sentence per row; structured details go in meta.
  • Every admin-surface mutation records a row — bulk operations record one row with the count and the affected ids in meta.