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 🙏

© 2025 – Pkg Stats / Ryan Hefner

slintorm

v0.1.5

Published

Minimal fully typed orm for typescript

Readme

Simple TypeScript ORM

A lightweight TypeScript ORM for SQLite, PostgreSQL, and MySQL.
Inspired by Go's GORM, this ORM focuses on simplicity, type safety, and full-featured query building while keeping the API intuitive for TypeScript developers.

It is designed for:

  • Rapid development with auto table creation and migrations
  • Fully type-safe model definitions
  • Easy handling of relationships: one-to-one, one-to-many, and many-to-many
  • Advanced query building with joins, aggregates, subqueries, window functions, and preloads
  • Minimal configuration with sensible defaults

Installation

npm install slintorm

Model Interfaces


/** Post table */
interface Post {
  // @index;
  id?: number;
  // @length:255;not null;comment:Post title
  title: string;
  // @nullable;comment:Author user ID
  userId?: number;
  // @relation manytoone:User;foreignKey:userId;onDelete:SET NULL
  user?: User;
  // @json;nullable;comment:Extra post data
  meta?: Record<string, any>;
  createdAt?: string;
  updatedAt?: string;
  // @softDelete
  deletedAt?: string;
  // @enum:(draft,published,archived)
  status?: "draft" | "published" | "archived";
}

/** User table */
interface User {
  // @index;auto;comment:primary key
  id?: number;
  // @nullable;length:100;comment:First name
  firstName?: string;
  // @length:100;not null;comment:Last name
  name: string;
  // @nullable;length:100;comment:Last name
  lastname?: string;
  // unique;comment:Email
  email?: string;
  // @relationship onetomany:Post;foreignKey:userId
  posts?: Post[];
  // @relationship onetoone:Profile;foreignKey:userId;onDelete:CASCADE
  profile?: Profile;
  // @json;nullable;comment:Extra user info
  meta?: Record<string, any>;
  createdAt?: string;
  updatedAt?: string;
  // @softDelete
  deletedAt?: string;
  // @enum:(active,inactive,banned)
  status?: "active" | "inactive" | "banned";
}

/** Profile table */
interface Profile {
  // @index;auto;comment:primary key
  id?: number;
  // @relation onetoone:User;foreignKey:userId
  user?: User;
  userId: number;
  // @json;nullable;comment:Extra profile data
  meta?: Record<string, any>;
  createdAt?: string;
  updatedAt?: string;
  // @softDelete
  deletedAt?: string;
  // @enum:(male,female, other)
  gender?: "male" | "female" | "other";
}

/** Todo table */
interface Todo {
  // @index;auto;comment:primary key
  id?: number;
  // @length:255;not null
  title: string;
  // @nullable;length:1000
  detail: string;
  createdAt?: string;
  updatedAt?: string;
  // @softDelete
  deletedAt?: string;
  // @json;nullable
  meta?: Record<string, any>;
  // @enum:(low,medium,high)
  priority?: "low" | "medium" | "high";
}

/** Task table */
interface Task {
  // @index;auto
  id?: number;
  // @length:255;not null
  title: string;
  // @nullable;length:1000
  detail: string;
  createdAt?: string;
  updatedAt?: string;
  // @softDelete
  deletedAt?: string;
  // @json;nullable
  meta?: Record<string, any>;
  // @enum:(todo,inprogress,done)
  status?: "todo" | "inprogress" | "done";
} 

/** Tasksx table */
interface Tasksx {
  // @index;auto
  id?: number;
  // @length:255;not null
  title: string;
  // @nullable;length:1000
  detail: string;
  createdAt?: string;
  updatedAt?: string;
  // @softDelete
  deletedAt?: string;
  // @json;nullable
  meta?: Record<string, any>;
  // @enum:(todo, inprogress, done)
  status?: "todo" | "inprogress" | "done";
}

/** Team table */
interface Team {
  // @index;auto
  id?: number;
  // @length:255;not null
  title: string;
  // @nullable;length:1000
  detail: string;
  // @nullable
  open?: boolean;
  // @nullable
  tested?: boolean;
  // @json;nullable
  meta?: Record<string, any>;
  createdAt?: string;
  updatedAt?: string;
  // @softDelete
  deletedAt?: string;
  // @enum:(active,archived)
  status?: "active" | "archived";
}

Initialization

import ORMManager from "slintorm";

// Initialize ORM
const orm = new ORMManager({
  driver: "sqlite",
  databaseUrl: "./test.db",
});

// Run migrations automatically
await orm.migrate();

Define Models


const Users = await orm.defineModel<User>("users", "User");
const Posts = await orm.defineModel<Post>("posts", "Post");
const Todos = await orm.defineModel<Todo>("todos", "Todo");
const Profiles = await orm.defineModel<Profile>("profiles", "Profile");
const Tasks = await orm.defineModel<Task>("tasks", "Task");
  const Teams = await orm.defineModel<Team>("team", "Team", {
    onCreateBefore(item) {
      console.log("before create Team: ", item)
    },
    onCreateAfter(item) {
      console.log("after create: ", item)
    },
    onUpdateAfter(oldData, newData) {

    },
  });


Basic CRUD Examples


// Insert
await Todos.insert({
  title: "To watch plates",
  detail: "Wash all plates",
  createdAt: new Date().toISOString(),
});

// Fetch all
const allTodos = await Todos.getAll();

// Fetch one
const user = await Users.get({ id: 1 });

// Update
await Users.update({ id: 1 }, { name: "Amike Catherine" });

// Update instance
const fetchedUser = await Users.get({ id: 1 });
await fetchedUser?.update({ name: "Amike Egwamene" });

// Delete
await Posts.delete({ id: 3 });

Query Builder Examples

// Preload relationships and filter
  const postWithUser = await Posts.query()
    .exclude("title")
    .preload("user")
    .preload("user.posts")
    .preload("user.profile")
    .preload("user.posts.user")
    .exclude("user.lastname")
    .first();


const userWithRelations = await Users.query()
  .preload("posts")
  .preload("profile")
  .first("id = 2");
  // .first({id: 2}); both are valid

// Nested preloads
const postWithUser = await Posts.query()
  .preload("user")
  .preload("user.posts")
  .preload("user.profile")
  .get();

// Filtering, ordering, and limiting
const todos = await Todos.query()
  .where("title", "LIKE", "%plates%")
  .orderBy("createdAt", "desc")
  .limit(5)
  .get();

// Distinct and aggregates
const counts = await Users.query()
  .count("id")
  .groupBy("lastname")
  .ILike("name", "jane")
  .get();

// Window function example
const rankedUsers = await Users.query()
  .window("ROW_NUMBER()", "PARTITION BY lastname ORDER BY id ASC")
  .get();

// Subquery example
const subquery = Users.query().select("id").where("name", "=", "Amike");
const usersFromSub = await Users.query()
  .selectSubquery(subquery, "sub_id")
  .get();

// EXISTS / NOT EXISTS example
const existsQuery = await Posts.query()
  .exists(subquery)
  .get();


   try {
    await Posts.delete({ id: 3 });
  } catch (err) {
    console.log("Delete error:", err);
  }


  const user = await Users.get({ id: 1 })
  // console.log("user: ", user)


  try {
    const uup = await Users.update({ id: 1 }, { name: "Amike Catherine" })
    //  console.log("immediate update: ", uup)

    const upuser = await Users.get({ id: 1 })
    // console.log("fetched updated user: ", upuser)
    const excupuser = await Users.query().exclude("profile")
    // .preload("posts").exclude("posts.user")
    .first()

    const updated = await upuser?.update({ name: "Amike Egwamene" });
    console.log("Updated user:", updated);
    console.log("excluded user fields:", excupuser);

  } catch (err) {
    console.log("error updated user: ", err)

  }


  const pp = await Profiles.query()
  .preload("user").preload("user.profile").preload("user.profile.user")
  .exclude("user.name")
  .first(`userId = ${2}`)
  console.log("profile: ", pp)


  const nnew = await Teams.insert({
    title: "To watch dishes",
    detail: "Wash all dishes",
    open: true,
    tested: false
  });
  console.log("Teams:", nnew);



Relationships

  • One-to-many: @relation onetomany:Post;foreignKey:userId
  • Many-to-one: @relation manytoone:User;foreignKey:userId
  • One-to-one: @relationship onetoone:Profile;foreignKey:userId
  • Many-to-many: Use a through table in schema metadata

Migrations

The ORM automatically ensures that tables exist and applies schema changes based on your model metadata. Use:

await orm.migrate();

to synchronize your database schema.


Why use Simple TypeScript ORM?

Many TypeScript ORMs are either minimal but lack features (like Drizzle) or extremely heavy (like Prisma). Simple TypeScript ORM balances ease of use, flexibility, and performance, making it ideal for projects that require quick iteration, full control over queries, and GORM-inspired patterns in TypeScript.

| Feature | Simple TypeScript ORM | Drizzle | Prisma | |---------------------------------|---------------------|----------------|---------------| | Auto table creation & migration | ✅ Automatic and zero-config migrations | ❌ Manual or CLI-based | ✅ CLI-based migrations | | Type-safe queries | ✅ Full TypeScript support | ✅ Type-safe | ✅ Type-safe | | Relationships (1:1,1:N,N:M) | ✅ Fully supported with preloads | ✅ Supported via join tables | ✅ Supported via relations | | Query builder with joins & HAVING| ✅ Advanced SQL capabilities | ❌ Limited | ✅ Limited to client API, raw SQL for complex cases | | Aggregates & window functions | ✅ COUNT, SUM, AVG, custom window functions | ❌ Limited | ✅ Raw SQL required for window functions | | Subquery support | ✅ Easy subquery integration | ❌ Limited | ✅ Possible via raw SQL | | Preload / eager loading | ✅ Preload nested relations | ❌ Not supported | ✅ Supports select/include | | Lightweight & minimal boilerplate| ✅ Minimal setup, single import | ✅ Minimal | ❌ Requires Prisma Client generation and schema setup | | Inspiration / design | GORM-like (Go ORM) | TypeScript-only | Prisma Engine with schema DSL | | Learning curve | ✅ Very low, intuitive | ✅ Low | ❌ Medium, requires learning schema DSL and client | | Flexibility / raw SQL | ✅ Direct SQL injection when needed | ✅ Limited raw SQL | ✅ Raw SQL available but requires Prisma Client | | Ideal use case | Rapid prototyping, small to medium apps, GORM-style workflow | Type-safe lightweight projects | Large-scale apps, strong typing, ecosystem-heavy projects |


Summary

SlintORM is best suited for developers who want a GORM-inspired workflow in TypeScript: minimal setup, automatic migrations, and full SQL query control.
Drizzle is lightweight and type-safe but lacks advanced query features.
Prisma is powerful and production-ready, but heavier and requires more boilerplate and tooling setup.

SlintORM fills the niche for quick iteration, flexible queries, and minimal friction, making it perfect for both learning and production projects.

Notes

  • Supports SQLite, PostgreSQL, and MySQL.
  • MongoDB support is limited to basic CRUD via the adapter.
  • All queries are type-safe and return mapped Boolean fields and excluded columns if configured.
  • Advanced query builder supports joins, group by, having, distinct, window functions, subqueries, and preloads.