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

@stingerloom/orm

v0.22.0

Published

A standalone, framework-agnostic TypeScript ORM that can be used with any Node.js framework

Downloads

919

Readme


Why Stingerloom?

  • Multi-tenancy built in — Layered metadata system (inspired by Docker OverlayFS) with AsyncLocalStorage-based context isolation. Zero cross-tenant leakage by design.
  • Typed QueryDSL via Proxy, no codegenqAlias(Entity, "u") gives you IDE autocomplete on every column. Chain .eq / .gt / .like / .in, aggregates (.count() / .sum() / .avg()), CAST, date components, window functions, CASE/WHEN (plus iff / mapValues / buckets shortcuts), subqueries, and JSON-path navigation — all returning type-safe expressions that compose freely across where() / having() / select(). Import the namespace as { Expressions as exp } to keep call sites short.
  • Unit of Work plugin — Identity Map, dirty checking, cascade, batch flush, lazy proxies, and pessimistic locking via em.extend(bufferPlugin()). Single-level cache skips round-trips for repeated PK lookups.
  • Three databases, one API — MySQL (incl. MariaDB-specific optimizations), PostgreSQL, and SQLite share the same EntityManager interface. Switch drivers without rewriting queries.
  • Schema Diff migrations — Compare live database state against entity metadata and auto-generate migration code. Supports true / "safe" / "dry-run" synchronize modes.
  • NestJS-ready — First-party module with @InjectRepository, @InjectEntityManager, and multi-DB named connections.

Quick Start

npm install @stingerloom/orm reflect-metadata
npm install pg        # or mysql2, better-sqlite3

tsconfig.json — decorator metadata must be on:

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

1. Define entities

import "reflect-metadata";
import {
  Entity, PrimaryGeneratedColumn, Column,
  ManyToOne, OneToMany, RelationColumn,
} from "@stingerloom/orm";

@Entity()
class Author {
  @PrimaryGeneratedColumn() id!: number;
  @Column() name!: string;
  @OneToMany(() => Post, p => p.author) posts!: Post[];
}

@Entity()
class Post {
  @PrimaryGeneratedColumn() id!: number;
  @Column() title!: string;
  @Column({ type: "int" }) views!: number;
  @Column({ type: "datetime" }) publishedAt!: Date;

  @ManyToOne(() => Author, a => a.posts)
  @RelationColumn({ name: "author_id" })
  author!: Author;
  authorId?: number;
}

2. Connect — same API across MySQL, PostgreSQL, and SQLite

import { EntityManager, bufferPlugin } from "@stingerloom/orm";

const em = new EntityManager();
await em.register({
  type: "postgres",          // or "mysql" / "sqlite" — query code stays identical
  host: "localhost", port: 5432,
  username: "postgres", password: "postgres", database: "app",
  entities: [Author, Post],
  synchronize: true,         // disable in production; use migrations instead
});

em.extend(bufferPlugin());   // enable Identity Map + dirty checking

3. Typed QueryDSL with JOIN — IDE autocomplete, no codegen, no string columns

import { qAlias } from "@stingerloom/orm";

const p = qAlias(Post, "p");
const a = qAlias(Author, "a");

const trending = await em.createQueryBuilder(p)
  .innerJoinRelation("author", "a")    // FK derived from @ManyToOne metadata — no ON clause needed
  .select([
    p.title.as("title"),
    a.name.as("author"),
    p.views.as("views"),
    p.publishedAt.year().as("yr"),
  ])
  .where(p.title.containsIgnoreCase("typescript"))
  .andWhere(p.views.gt(100))
  .andWhere(a.name.startsWith("J"))
  .orderBy(p.views.desc())
  .getRawMany();

Every reference to p.title, a.name, p.publishedAt.year(), etc. is resolved against the entity at compile time — typo a column name and TypeScript fails the build. innerJoinRelation reads the FK from the @ManyToOne metadata so you never write the join condition by hand.

4. Unit of Work — Identity Map + dirty checking

const buf = em.buffer();

const p1 = await buf.findOne(Post, { where: { id: 1 } });
const p2 = await buf.findOne(Post, { where: { id: 1 } });
console.log(p1 === p2);   // true — second lookup hits the Identity Map, no extra SELECT

p1!.views = 500;
await buf.flush();        // BEGIN → single UPDATE with only the dirty column → COMMIT

5. Multi-tenancy — AsyncLocalStorage-scoped, zero leakage

import { MetadataContext } from "@stingerloom/orm";

await MetadataContext.run("tenant_a", async () => {
  const posts = await em.find(Post);
  // every metadata lookup inside this frame resolves through tenant_a's
  // overlay layer first, then falls through to the public layer
});

A NestJS interceptor or Express middleware wraps the per-request handler in MetadataContext.run(tenantId, …) — concurrent requests for different tenants stay isolated by AsyncLocalStorage with no shared mutable state.

See the Getting Started guide for full setup, and the nestjs-multitenant / nestjs-linear-clone examples for production-shaped tenant wiring.

Features

| Category | Highlights | |----------|------------| | Modeling | @Entity, @Column, @ManyToOne, @OneToMany, @ManyToMany, @OneToOne, eager/lazy loading, inheritance mapping (STI / TPT / TPC), UUID columns with UUIDv7 | | Querying | find, findOne, findWithCursor, findAndCount, SelectQueryBuilder with JOIN / GROUP BY / HAVING; qAlias() typed expression chain — string / numeric / math helpers, CAST, date arithmetic + components, window functions, CASE WHEN, subquery operators, JSON-path navigation, raw SQL escape hatches | | Mutations | save, update, delete, softDelete, restore, upsert, batchUpsert, streamBatch, batch operations | | Transactions | @Transactional decorator, manual BEGIN / COMMIT / ROLLBACK, savepoints, isolation levels, deadlock retry, NOWAIT / SKIP LOCKED | | Unit of Work | em.extend(bufferPlugin()) — Identity Map, dirty checking, cascade, batch flush, lazy proxies, pessimistic locking, @Version optimistic locking | | Multi-tenancy | Layered metadata (OverlayFS model), MetadataContext.run(), PostgreSQL schema isolation, TenantMigrationRunner | | Migrations | SchemaDiff auto-detection (column rename heuristic), MigrationGenerator, CLI runner (npx stingerloom migrate:run \| rollback \| status \| generate) | | Observability | N+1 detection, slow query warnings, EXPLAIN analysis, EntitySubscriber events, query listeners | | Validation | @NotNull, @MinLength, @MaxLength, @Min, @Max, Zod / Valibot schemas via qb.selectSchema(schema) | | Schema definition | Decorators or decorator-free EntitySchema; Prisma schema import | | Infrastructure | Connection pooling, read replicas, retry with backoff, per-query timeout, graceful shutdown, SSL/TLS, AsyncIterable streaming (stream()), MariaDB native UUID + INSERT … RETURNING | | NestJS | StingerloomOrmModule.forRoot / forFeature, @InjectRepository, @InjectEntityManager, multi-DB named connections |

Database Support

| | MySQL / MariaDB | PostgreSQL | SQLite | | --------------------------- | :-------------: | :--------------: | :----: | | CRUD | ✓ | ✓ | ✓ | | Transactions | ✓ | ✓ | ✓ | | Schema Sync | ✓ | ✓ | ✓ | | Migrations | ✓ | ✓ | ✓ | | ENUM | ✓ | ✓ (native + sync)| — | | JSON path queries | ✓ | ✓ (jsonb) | ✓ | | Full-text search | ✓ | ✓ | — | | Window functions | ✓ | ✓ | ✓ | | Schema Isolation | — | ✓ | — | | Read Replica | ✓ | ✓ | — | | INSERT … RETURNING | MariaDB 10.5+ | ✓ | ✓ | | Native UUID storage | MariaDB 10.7+ | ✓ | — (TEXT)| | SSL / TLS | ✓ | ✓ | — |

Examples

Example projects are included in examples/:

| Project | Description | |---------|-------------| | nestjs-cats | CRUD, relations, soft delete, cursor pagination, EntitySubscriber | | nestjs-blog | ManyToMany, upsert, 59 e2e tests (Users / Posts / Tags / Categories) | | nestjs-todo | Minimal CRUD — uses the published npm package | | nestjs-todo-sqlite | Minimal CRUD on SQLite via better-sqlite3 | | nestjs-multitenant | PostgreSQL schema-based tenant isolation with TenantMigrationRunner | | prisma-import-demo | Generate Stingerloom entities from an existing Prisma schema |

Contributing

Contributions are welcome. Please open an issue first to discuss what you'd like to change.

License

MIT