npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@kilnjs/audit

v0.1.3

Published

An audit trail for KilnJS: every write recorded with the caller that made it, from the driver rather than from a call site

Readme

@kilnjs/audit

An audit trail for KilnJS, as a module core is given rather than one it contains. Every write is recorded with the caller that made it, from inside the database driver.

yarn add @kilnjs/audit
// app.config.ts
import { audit } from "@kilnjs/audit";

export default {
  modules: [audit()],
} satisfies AppConfig;
// src/audit.ts
import database from "@kilnjs/core/database";
import type { AuditDelegate } from "@kilnjs/audit";

export default {
  models: ["User", "Membership", "Grant"],

  record: async (entry) => {
    await database.auditEntry.create({
      data: {
        model: entry.model,
        operation: entry.operation,
        actor_id: entry.actorId ?? null,
        at: entry.at,
      },
    });
  },
} satisfies AuditDelegate;

Why it is a module and not a recipe

Because the two hard parts cannot be done at a call site.

Nothing can be forgotten. The recording happens in the database driver, so a write through a model method, a job, a seed or the Prisma CLI is recorded the same way. A trail that each endpoint had to add to has its gaps exactly where somebody was in a hurry, and those are the writes worth having a trail of.

The caller arrives on its own. A write three layers inside a model method knows nothing about who asked for it. This reads the principal out of the request context core opens, so no signature changes and nothing is threaded through.

A write with no request behind it - a job, a seed, a migration - is recorded with no actor at all. That is a fact rather than a gap: inventing one would make the trail say something untrue.

What a delegate decides

| | | | --- | --- | | record | where an entry goes. The only thing it must have | | models | which models to record. Everything, unless it says | | operations | which operations. Every write, unless it says. Reads are never recorded |

A server that audits everything records its own sessions table on every request, which is noise rather than evidence, so most applications name the models that matter.

What audit() takes

The same two questions the delegate answers, plus three the delegate cannot. What is passed here wins over the delegate, so a deployment can narrow a delegate it did not write.

audit({
  skipModels: ["Session", "RequestLog"],
  actorId: (context) => context?.user?.id ?? "system",
});

| | | | --- | --- | | models | which models to record, over the delegate's own list | | operations | which operations, over the delegate's | | skipModels | never recorded, whatever either of the above said | | unrecorded | refuse or report: what a write whose entry could not be kept does. AUDIT_UNRECORDED | | actorId | who a write is recorded against, where it is not the caller's own id | | delegate | one passed rather than discovered, for a server that assembles it |

skipModels is there because narrowing by inclusion means listing every model that matters and remembering the next one; a server that audits everything except two tables says that instead.

actorId is handed the request-scoped context and is the only way to record something other than context.user.id: an impersonated caller, a service account, or the word system for a write with no request behind it.

Two decisions worth knowing about

A write whose entry could not be recorded fails. A trail is only as good as its gaps, so the caller is told rather than quietly left with an untracked write. unrecorded: "report" is the other answer, for a server that would rather lose the entry than the write, and the failure is reported to telemetry either way.

Recording cannot record itself. The delegate writes its entry through the same client this wraps. The guard is async storage rather than a flag, because record is awaited and two requests overlap: a boolean would be switched off by whichever finished first.

A server with this module declared and no src/audit.ts does not boot. It is not a module with a missing option: it is a server that believes it is keeping a trail and is not, which is the worst of the three states.

Verifying

yarn verify   # ts:check, lint, build, exports:check, test

No database: what the real client does with an extension is core's business and the reference application's. What this module hands over is what these tests cover, including that the delegate's own writes stay out of the trail however deep they go.