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

@cossackframework/database

v0.8.2

Published

Serverless-first Active Record ORM for Cossack

Readme

@cossackframework/database

An ESM-only, serverless-first Active Record ORM for Cossack. It provides decorated models, safe SQL, a fluent query builder, explicit request scopes, schema metadata, introspection, and deterministic migrations without a repository layer or a runtime schema-sync mode.

Install

pnpm add @cossackframework/database reflect-metadata

Install only the optional driver used by the application (pg, mysql2, @tursodatabase/database for embedded/Desktop, @tursodatabase/serverless for remote Turso, or better-sqlite3). Node 22's built-in node:sqlite, Bun SQL, and Cloudflare D1 need no third-party database driver.

Use TypeScript legacy decorators:

{
  "compilerOptions": {
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true,
    "useDefineForClassFields": false
  }
}

Documentation

Models and request scope

import "reflect-metadata";
import {
  BaseEntity,
  Column,
  CreateDateColumn,
  Entity,
  PrimaryGeneratedColumn,
  createORM,
} from "@cossackframework/database";
import { nodeSQLite } from "@cossackframework/database/node";

@Entity()
export class User extends BaseEntity {
  @PrimaryGeneratedColumn()
  declare id: number;

  @Column()
  declare email: string;

  @Column({ type: "json" })
  declare preferences: Record<string, unknown>;

  @CreateDateColumn()
  declare createdAt: Date;
}

const orm = createORM({
  adapter: await nodeSQLite({ filename: "app.db" }),
  entities: [User],
});

await orm.run(async () => {
  const user = User.create({
    email: "[email protected]",
    preferences: { theme: "dark" },
  });
  await user.save();

  const active = await User.query("user")
    .where((where) => where.like("email", "%@example.com"))
    .orderBy("createdAt", "desc")
    .limit(20)
    .getMany();
});

Static Active Record methods and the global sql tag deliberately throw outside orm.run(). Put one scope around each request, queue job, scheduled task, or CLI operation. Runtime adapters provide async-local isolation; a nested transaction rebinds the scope to its transaction client and uses savepoints when supported.

Safe SQL

import { sql } from "@cossackframework/database";

await orm.run(async () => {
  const email = "[email protected]"; // Treat this as untrusted user input.
  const result = await sql`
    SELECT ${sql.id("id")}, ${sql.id("email")}
    FROM ${sql.id("users")}
    WHERE ${sql.id("email")} = ${email}
  `;
});

Values always become parameters. sql.id() is the identifier escape hatch, sql.fragment composes queries, sql.join() builds lists, and sql.values() builds object/bulk insert tuples. sql.unsafe() is the only API that injects literal SQL.

new SQL({ adapter }) creates a standalone Bun-compatible tagged client. In Node, new SQL("postgres://…"), new SQL("mysql://…"), new SQL("https://….turso.io"), and SQLite paths select an adapter lazily. Workers intentionally require a binding or explicit adapter rather than environment URL guessing.

Relations

Relations load only when requested:

const users = await User.find({
  where: { enabled: true },
  relations: ["roles"],
});

The loader batches keys and chunks them at the adapter's parameter limit. Owning relations expose both logical metadata and physical join columns. Many-to-many associations require @JoinTable() on one side. Cascades are opt-in with { cascade: ["insert", "update"] }; delete cascades remain database behavior and are never replayed across an in-memory object graph.

Runtime adapters

| Runtime entry | Adapters | | --- | --- | | @cossackframework/database/node | nodeSQLite, betterSQLite, postgres, mysql, turso | | @cossackframework/database/deno | deno, denoSQLite, postgres, mysql, turso | | @cossackframework/database/bun | bun over the documented Bun SQL core API | | @cossackframework/database/cloudflare | d1, hyperdrivePostgres, hyperdriveMySQL | | @cossackframework/database/deno | deno with an injected remote or SQLite driver | | @cossackframework/database/adapter | public dialect, driver, result, scope, and capability contracts | | @cossackframework/database/cossack | middleware plus database cache/session stores |

D1 uses prepared statements and batch(). It supports atomic migration batches, but rejects interactive transactions, savepoints, and connection reservation with UnsupportedCapabilityError. Enable Workers' narrow nodejs_als compatibility flag for request scope isolation.

Hyperdrive creates and closes a request-local pg/mysql2 client while Hyperdrive owns the global pool. Workers need nodejs_compat; mysql2 uses disableEval: true. See Cloudflare's current D1 binding API, Hyperdrive guide, and Workers best practices.

Schema and migrations

orm.config.ts is the single configuration used by the CLI and Studio:

import { defineConfig } from "@cossackframework/database";
import { nodeSQLite } from "@cossackframework/database/node";
import { entities } from "./src/entities/index.js";
import { migrations } from "./migrations/index.js";
import { seeders as seeds } from "./seeders/index.js";

export default defineConfig({
  entities,
  migrations,
  migrationDirectory: "./migrations",
  seeds,
  adapter: () => nodeSQLite({ filename: "app.db" }),
});
cossack-orm migration snapshot
cossack-orm migration generate add_users
cossack-orm migration squash 0001_schema --prune
cossack-orm migration up
cossack-orm migration down
cossack-orm migration status
cossack-orm migration check
cossack-orm migration baseline
cossack-orm schema pull
cossack-orm schema diff
cossack-orm schema check
cossack-orm seed list
cossack-orm seed run
cossack-orm seed run --only users,posts

Generated migrations compare current decorators with a committed model schema snapshot; database introspection remains limited to schema diff/check/pull. Migrations are reviewable TypeScript and are never run during application startup. Dropped tables/columns and narrowing changes require --allow-destructive. Rename detection is never heuristic: set renamedFrom on the entity or column. _cossack_migrations stores the migration name, SHA-256 checksum, batch, and application timestamp.

orm.schema() returns versioned, serializable OrmSchema metadata. Studio can merge it with orm.introspect() to display logical types and virtual relations even where SQLite's physical affinity is less specific.

Seeders

Declare named seeders and keep their execution order in one exported array:

import {
  SeederRunner,
  defineSeeder,
} from "@cossackframework/database";

export const usersSeeder = defineSeeder({
  name: "users",
  transaction: "auto",
  async run({ orm, sql, signal }) {
    // Active Record calls are already in ORM scope.
  },
});

export const seeders = [usersSeeder] as const;

const results = await new SeederRunner(orm, seeders).run({
  only: ["users"],
});

Seeders run sequentially in configuration order and stop at the first failure. "auto" uses one transaction per seeder where supported, "required" fails before writing when interactive transactions are unavailable, and "none" executes without a runner-managed transaction. On D1, "auto" runs without an interactive transaction; use a database-specific batch inside a "none" seeder when the work must be atomic.

SeederRunner owns scope, selection, transaction policy, cancellation, and failure attribution. Framework CLIs should load orm.config.ts and delegate to this runner instead of implementing their own seed loop. Environment and production-confirmation policies belong in the framework CLI. Seed data remains application-owned and should be idempotent through stable keys, existence checks, or upserts; seeders are not recorded as migrations.

Deliberate v1 boundaries

There is no Data Mapper/repository API, legacy query-builder compatibility layer, automatic schema mutation at startup, NoSQL/GraphQL integration, or ORM query-result cache. Database-specific operations remain available through safe SQL fragments and custom third-party adapters.

Complete SQLite example

examples/sqlite-starter contains related User and Post models, a versioned migration, an idempotent seeder, and executable create/migrate/seed/query scripts:

pnpm example:sqlite
pnpm exec tsx ./examples/sqlite-starter/query.ts

examples/multiple-connections demonstrates two independent SQLite connections with separate models, migrations, transactions, CLI configs, and concurrent explicit-manager queries:

pnpm example:multiple-connections