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

@dannyfuf/persistence

v0.1.0

Published

Active Record-inspired persistence layer over Knex for PostgreSQL.

Readme

Persistence

Persistence is a small Active Record-inspired TypeScript layer over Knex for PostgreSQL. The current MVP runtime includes generated schema consumption, hydrated reads, lifecycle-aware saves, explicit transactions, and commit callbacks.

Installation

npm install @dannyfuf/persistence knex pg zod
pnpm add @dannyfuf/persistence knex pg zod
bun add @dannyfuf/persistence knex pg zod

Development Setup

pnpm install
docker compose up -d postgres
pnpm lint
pnpm typecheck
pnpm test
pnpm test:integration
pnpm schema:check
pnpm build

If Docker is provided by OrbStack and the shim is not on your shell PATH, add ~/.orbstack/bin before running Docker commands.

Configuration

Create a project-level persistence.config.mjs:

export default {
  knex: {
    client: "pg",
    connection: process.env.DATABASE_URL,
  },
  schema: {
    outputPath: "src/generated/persistenceSchema.ts",
    databaseSchemas: ["public"],
  },
};

The knex field can also be a path to a module that exports a Knex config.

Schema Commands

Run migrations through Persistence to update the database and regenerate the schema artifact in one step:

pnpm cli migrate:latest

The command runs knex.migrate.latest() from your Persistence config and only generates schemas after migrations succeed. If migrations fail, the generated schema file is left untouched.

You can also run schema generation commands directly for CI and debugging. The migrated PostgreSQL database is still the source of truth.

pnpm cli schema:generate
pnpm cli schema:check

schema:generate writes the generated artifact. schema:check regenerates it in memory and exits non-zero when the checked-in file is missing or stale.

See docs/schema-generation.md for the generated file contract and PostgreSQL support notes. See docs/mvp-boundaries.md for the deliberate MVP limits.

Verification

The local CI equivalent is:

pnpm verify

pnpm verify runs lint, type-checking, unit tests, PostgreSQL integration and e2e tests, schema check, and build. The repository-level pnpm schema:check script prepares the PostgreSQL fixture schema before checking test/fixtures/generated/phase1-schema.ts.

Models And Reads

Configure the runtime with your Knex connection and generated schema module:

import { Model, defineModel, type ModelInstance } from "@dannyfuf/persistence";
import { knex } from "./db.js";
import { persistenceSchema } from "./generated/persistenceSchema.js";

Model.configure({ connection: knex, schema: persistenceSchema });

class UserRecord extends Model<"users"> {
  static tableName = "users" as const;
}

export const User = defineModel(UserRecord);
export type User = ModelInstance<UserRecord>;

Read APIs hydrate rows into model instances with direct database-column accessors:

const user = await User().find(1);
const activeUsers = await User().where({ active: true }).orderBy("email").all();

Nullable reads return null; findOrThrow, findByOrThrow, and firstOrThrow throw RecordNotFoundError.

Direct bulk operations execute SQL without lifecycle behavior:

await User().where({ active: false }).update({ active: true });
await User().where({ id }).delete();

Bulk update validates the generated update shape, but skips model validation, callbacks, commit callbacks, hydration, and automatic updated_at touches.

See docs/models-and-queries.md for callable repositories, build, custom repository methods, transaction-bound repositories, and advanced query() examples. See docs/api.md for the supported public exports.

Saves And Transactions

Use create, assign, save, and assignAndSave for lifecycle-aware writes:

const user = await User().create({
  email: "[email protected]",
  external_id: "00000000-0000-4000-8000-000000000001",
});

await user.assignAndSave({ name: "Alex" });

Use Model.transaction and pass tx explicitly to repositories that should participate in the same transaction:

await Model.transaction(async (tx) => {
  const user = await User(tx).findOrThrow(1);
  await user.assignAndSave({ active: false });
});

See docs/lifecycle.md for validation, timestamps, dirty tracking, and callback order. See docs/transactions.md for transaction-bound instances, nested transaction squashing, and commit callback failure behavior.

Callbacks

Callbacks are static methods decorated on the record class:

import { BeforeSave, AfterCommit } from "@dannyfuf/persistence";

class UserRecord extends Model<"users"> {
  static tableName = "users" as const;

  @BeforeSave
  static normalizeEmail(user: User) {
    user.email = user.email.toLowerCase();
  }

  @AfterCommit
  static publishChange(user: User) {
    console.log("committed", user.id);
  }
}

Example

See examples/basic for a minimal PostgreSQL project with a Knex migration, Persistence config, generated-schema command, model definition, queries, lifecycle saves, transactions, and direct bulk operations.