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

accio-orm

v0.1.1

Published

The summoning charm for Postgres - A lightweight TypeScript ORM

Readme

Accio 🪄

The summoning charm for Postgres

Accio is a lightweight, type-safe TypeScript ORM for PostgreSQL, built from first principles with a focus on simplicity and developer experience.

npm version TypeScript License: MIT

Features

  • Decorator-based entity definitions
  • 🔒 Type-safe queries with TypeScript
  • 🎯 Simple and intuitive API
  • 🔗 Fluent query builder with method chaining
  • 📦 Zero dependencies (except pg and reflect-metadata)
  • 🎨 Data Mapper pattern for clean architecture
  • 🚀 Connection pooling built-in
  • 💪 Full CRUD operations out of the box

Installation

  1. Install the package
npm install accio-orm
  1. Install pg (postgres driver)
npm install pg
  1. Install reflect-metadata
npm install reflect-metadata

Important: Import reflect-metadata

You must import reflect-metadata once at your application's entry point (before any Accio code runs):

// src/index.ts or src/main.ts
import 'reflect-metadata';

// Now you can use Accio
import { connect, Table, Column, PrimaryColumn } from 'accio-orm';

Why? TypeScript decorators require reflect-metadata to be loaded globally before any decorator-decorated classes are loaded. This enables Accio to read metadata from your @Table and @Column decorators.

Note: pg (^8.0.0) and reflect-metadata (^0.2.2) are peer dependencies and must be installed separately.

Prerequisites

  • Node.js 16+
  • PostgreSQL 12+
  • TypeScript 5+

Quick Start

1. Configure TypeScript

Add these to your tsconfig.json:

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

2. Define Your Entity

import 'reflect-metadata';
import { Table, Column, PrimaryColumn } from 'accio-orm';

@Table('users')
class User {
  @PrimaryColumn()
  id!: number;

  @Column()
  name!: string;

  @Column()
  age!: number;

  @Column()
  email!: string;
}

3. Connect to Database

import { connect } from 'accio-orm';

const db = connect({
  host: 'localhost',
  port: 5432,
  database: 'mydb',
  user: 'postgres',
  password: 'password'
});

4. Use the Repository

const userRepo = db.getRepository(User);

// Create
const user = new User();
user.name = 'Alice';
user.age = 25;
user.email = '[email protected]';
await userRepo.save(user);

// Read
const foundUser = await userRepo.findById(1);
const allUsers = await userRepo.findAll();

// Update
foundUser.age = 26;
await userRepo.save(foundUser);

// Delete
await userRepo.delete(foundUser);

API Documentation

Decorators

@Table(tableName: string)

Marks a class as a database entity.

@Table('users')
class User {
  // ...
}

@PrimaryColumn()

Marks a property as the primary key column.

@PrimaryColumn()
id!: number;

@Column(options?)

Marks a property as a database column.

Options:

  • name?: string - Custom column name (default: property name)
  • nullable?: boolean - Whether the column can be null (default: true)
  • type?: string - Database type hint (optional)
@Column()
name!: string;

@Column({ name: 'user_email', nullable: false })
email!: string;

Connection

connect(config: ConnectionConfig): Connection

Creates a connection to the database.

const db = connect({
  host: 'localhost',
  port: 5432,
  database: 'mydb',
  user: 'postgres',
  password: 'password',
  max: 10 // optional: max connections in pool
});

connection.getRepository<T>(entityClass): Repository<T>

Gets a repository for an entity.

const userRepo = db.getRepository(User);

connection.close(): Promise<void>

Closes all database connections.

await db.close();

Repository

Basic Operations

findById(id): Promise<T | null>

Find an entity by its primary key.

const user = await userRepo.findById(1);

findAll(): Promise<T[]>

Find all entities.

const users = await userRepo.findAll();

save(entity): Promise<T>

Insert or update an entity (smart save).

const user = new User();
user.name = 'Bob';
await userRepo.save(user); // Insert

user.age = 30;
await userRepo.save(user); // Update

insert(entity): Promise<T>

Explicitly insert a new entity.

await userRepo.insert(user);

update(entity): Promise<T>

Explicitly update an existing entity.

await userRepo.update(user);

delete(entity): Promise<void>

Delete an entity.

await userRepo.delete(user);

deleteById(id): Promise<void>

Delete by primary key.

await userRepo.deleteById(1);

count(): Promise<number>

Count all entities.

const total = await userRepo.count();

exists(id): Promise<boolean>

Check if an entity exists by ID.

const exists = await userRepo.exists(1);

Query Builder

where(conditions): QueryBuilder<T>

Add WHERE conditions (can be chained, combined with AND).

// Single condition
const users = await userRepo.where({ age: 25 }).find();

// Multiple properties (AND)
const users = await userRepo.where({ age: 25, city: 'NYC' }).find();

// Chain multiple where() calls (AND)
const users = await userRepo.where({ age: 25 }).where({ city: 'NYC' }).find();

// Array values (IN clause)
const users = await userRepo.where({ age: [25, 30, 35] }).find();

// NULL values
const users = await userRepo.where({ middleName: null }).find();

orderBy(column, direction?): QueryBuilder<T>

Order results by a column.

const users = await userRepo.where({ age: 25 }).orderBy('name', 'ASC').find();

// DESC order
const users = await userRepo.orderBy('age', 'DESC').find();

limit(n): QueryBuilder<T>

Limit the number of results.

const users = await userRepo.where({ age: 25 }).limit(10).find();

offset(n): QueryBuilder<T>

Skip the first N results (for pagination).

const users = await userRepo.where({ age: 25 }).offset(20).limit(10).find();

Terminal Operations

find(): Promise<T[]>

Execute the query and return all results.

const users = await userRepo.where({ age: 25 }).find();

findOne(): Promise<T | null>

Execute the query and return the first result.

const user = await userRepo.where({ email: '[email protected]' }).findOne();

count(): Promise<number>

Count matching results.

const count = await userRepo.where({ age: 25 }).count();

exists(): Promise<boolean>

Check if any results exist.

const exists = await userRepo.where({ email: '[email protected]' }).exists();

toSQL(): { sql: string; params: unknown[] }

Get the SQL that would be executed (for debugging).

const { sql, params } = userRepo.where({ age: 25 }).toSQL();
console.log(sql); // SELECT * FROM users WHERE age = $1
console.log(params); // [25]

Examples

Pagination

const page = 1;
const pageSize = 10;

const users = await userRepo
  .orderBy('name', 'ASC')
  .offset((page - 1) * pageSize)
  .limit(pageSize)
  .find();

const total = await userRepo.count();
const totalPages = Math.ceil(total / pageSize);

Search

const results = await userRepo
  .where({ city: 'NYC' })
  .orderBy('age', 'DESC')
  .limit(20)
  .find();

Complex Queries

const users = await userRepo
  .where({ age: [25, 30, 35] })
  .where({ city: 'NYC' })
  .orderBy('name', 'ASC')
  .limit(10)
  .find();

Database Setup

Create your tables manually (Accio is schema-agnostic):

CREATE TABLE users (
  id SERIAL PRIMARY KEY,
  name TEXT NOT NULL,
  age INTEGER NOT NULL,
  email TEXT NOT NULL UNIQUE
);

Design Philosophy

Accio follows the Data Mapper pattern, keeping your domain models clean and separate from persistence logic. This means:

  • 📦 Entities are just classes - no methods for database operations
  • 🔧 Repositories handle persistence - clear separation of concerns
  • 🎯 Type-safe queries - TypeScript catches errors at compile time
  • 🪶 Lightweight - minimal abstractions, close to SQL

Roadmap

  • [ ] Relationships (one-to-many, many-to-many)
  • [ ] Transactions support
  • [ ] Advanced query operators (LIKE, >, <, !=, OR)
  • [ ] Schema migrations
  • [ ] Lifecycle hooks (beforeInsert, afterUpdate)
  • [ ] Validation decorators
  • [ ] Query result caching
  • [ ] Soft deletes
  • [ ] Automatic timestamps (createdAt, updatedAt)

Contributing

Contributions are welcome! This is a learning project, so feel free to:

  • Report bugs
  • Suggest features
  • Submit pull requests
  • Improve documentation

License

MIT

Acknowledgments

Built as a learning project to understand ORM internals, design patterns, and TypeScript decorators.

Inspired by TypeORM, Prisma, and other great ORMs in the ecosystem.


Accio! ⚡ Summon your data with ease.