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

@sunjc826/mini-orm

v0.2.1

Published

Test implementation of various enterprise application patterns

Readme

mini-orm

A PostgreSQL data mapper based object relational mapper supporting features like batched CRUD, lazy loading and single table inheritance.

Getting started

You only need to create 3 categories of objects before starting to manipulate database rows using objects.

The code snippets used here are taken from library tests.

Table

The table represents the state of your table in the database. There should be 1 XXXTable object per database table. You can create this object using the createTable function. The options of the arguments passed to createTable is as follows:

  • tableName The snakecasedTableName or camel_cased_table_name, either is fine.
  • columns A hash of snakecasedColumnName to column options, which vary depending on the type of columns. See below for the full list of column options supported. In general, the user can expect universal options like nullable to be on all column types.
const AuthorTable = createTable({
  tableName: "authors",
  columns: {
    id: {
      type: "serial",
      options: {
        primaryKey: true,
      },
    },
    name: {
      type: "text",
      options: {
        nullable: false,
      },
    },
    age: {
      type: "int",
    },
  },
});

Model/Domain Object

In the data mapper pattern, we do not work directly with database rows, but rather domain objects that are more decoupled from the database table structures. Here, you can use the createDomainObject function to create a domain object class to inherit from.

The fields that you want your domain object to have are defined as instance attributes of your domain object class.

Instances created from the below Author class will have the attributes name, age and books.

class Author extends createDomainObject<Author>({ domainKey: "author" }) {
  name: string;
  age: number;
  books: HasMany<Book>;
}

For the basic usecase, it is very easy to use the createDomainObject helper. Simply pass a string, which we will refer to as the domainKey as an option to the createDomainObject helper.

A quick explanation on what is the domainKey

  • In a data mapper pattern, domain objects are decoupled from the underlying tables that are persisted on disk, however, there must still be some ways for the user to indicate which tables are associated with which domain object.
  • The domainKey is a key that makes this link.

Single Table Inheritance

Example:

class Footballer extends extendDomainObject<Footballer>()({
  domainKey: FOOTBALLER,
  ParentDomainObject: Player,
}) {
  club: string;
}

Data Mapper

Now that we have the table and the model, we need some way to translate the a table row to a model object, and vice versa. You can use the createMapper function to create a mapper class.

const AuthorMapper = createMapper({
  domainKey: "author",
  Table: AuthorTable,
  hasMany: {
    books: {},
  },
});

Registry

Finally, now we have a table, data mapper and domain object. Recall the above section where it is mentioned that domainKey associates a triplet of these together.

In our example, this would be AuthorTable, AuthorMapper and Author.

We will declare this association to the registry and register this triplet by the following command.

registry.register("author", AuthorTable, Author, AuthorMapper);

Using the models

After defining the table, data mapper and domain object, you can generally forget about the former two. Most interactions should only involve the domain object.

The basic CRUD operations are as follows.

Query

Queries are done in an ActiveRecord sort of manner, though the underlying pattern is that of a datamapper.

/**
 * Search by id
 */
let author = await Author.findById(1);

/**
 * Search by other sql conditions
 */
// if you do not specify an operator, it will default to equals
author = await Author.find({
    domainObjectField: "name",
    value: "TestAuthor",
});

Insert

Inserts are done using a create method available on every domain object class.

/**
 * Creates an author with name 'TestAuthor' and age 30
 */
Author.create({ name: "TestAuthor", age: 30 });

/**
 * This is needed to save your newly created object to the database.
 */
await DomainObject.commit();
// You can do this.
await Author.commit();
// or this.
await AnyOtherClassInheritingFromDomainObject.commit();

Update

/**
 * Updates author's age attribute to 55.
 */
author.update({ age: 55 });

/**
 * Like insert, this is needed for database persistence.
 */
await DomainObject.commit();

Delete

/**
 * Deletes author.
 */
author.destroy();

/**
 * Like insert and update, this is needed for database persistence.
 */
await DomainObject.commit();

Commit

You can chain a bunch of inserts, updates and deletes before calling commit, which will notify the database of these changes in a single transaction.

Full code snippet

No inheritance

const AUTHOR = "author";
const BOOK = "book";
const PUBLISHER = "publisher";

// tables
const AuthorTable = createTable({
  tableName: "authors",
  columns: {
    id: {
      type: "serial",
      options: {
        primaryKey: true,
      },
    },
    name: {
      type: "text",
      options: {
        nullable: false,
      },
    },
    age: {
      type: "int",
    },
  },
});

const BookTable = createTable({
  tableName: "books",
  columns: {
    id: {
      type: "serial",
      options: {
        primaryKey: true,
      },
    },
    name: {
      type: "text",
    },
    genre: {
      type: "varchar",
      options: {
        limit: 20,
      },
    },
    authorId: {
      type: "int",
      options: {
        references: {
          domainKey: "author",
          tableColumnKey: "id",
        },
      },
    },
  },
});

const PublisherTable = createTable({
  tableName: "publishers",
  columns: {
    id: {
      type: "serial",
      options: {
        primaryKey: true,
      },
    },
    region: {
      type: "text",
    },
    bookId: {
      type: "int",
      options: {
        references: {
          domainKey: "book",
          tableColumnKey: "id",
        },
      },
    },
  },
});

// mappers
const AuthorMapper = createMapper({
  domainKey: AUTHOR,
  Table: AuthorTable,
  hasMany: {
    books: {},
  },
});

const BookMapper = createMapper({
  domainKey: BOOK,
  Table: BookTable,
  belongsTo: {
    author: {},
  },
  hasOne: {
    publisher: {},
  },
});

const PublisherMapper = createMapper({
  domainKey: "publisher",
  Table: PublisherTable,
  belongsTo: {
    book: {},
  },
});

// models
class Author extends createDomainObject<Author>({ domainKey: AUTHOR }) {
  name: string;
  age: number;
  books: HasMany<Book>;
}

class Book extends createDomainObject<Book>({ domainKey: BOOK }) {
  name: string;
  genre: string;
  authorId: string;
  author: BelongsTo<Author>;
  publisher: HasOne<Publisher>;
}

class Publisher extends createDomainObject<Publisher>({
  domainKey: PUBLISHER,
}) {
  region: string;
  // this is only for testing purposes, of course in an actual db
  // it makes no sense for a publisher to belong to a book
  book: BelongsTo<Book>;
}

Single Table Inheritance

// Domain Keys
const PLAYER = "player";
const FOOTBALLER = "footballer";

// Table
const PlayerTable = createTable({
  tableName: "players",
  columns: {
    id: {
      type: "serial",
      options: {
        primaryKey: true,
      },
    },
    name: {
      type: "text",
      options: {
        nullable: false,
      },
    },
    club: {
      // footballer
      type: "text",
    },
    battingAverage: {
      // cricketer, bowler
      type: "numeric",
    },
    bowlingAverage: {
      // bowler
      type: "numeric",
    },
  },
  singleTableInheritance: true,
});

// Data Mapper
const PlayerMapper = createMapper({
  domainKey: PLAYER,
  Table: PlayerTable,
  customInheritanceOptions: {
    variant: "singleTable",
    ParentMapper: null,
  },
  customColumnMap: {
    name: "name",
  },
});

const FootballerMapper = createMapper({
  domainKey: FOOTBALLER,
  Table: PersonTable,
  customInheritanceOptions: {
    variant: "singleTable",
    ParentMapper: PlayerMapper,
  },
  customColumnMap: {
    club: "club",
  },
});

// Models
class Player extends createDomainObject({ domainKey: PLAYER }) {
  name: string;
}

class Footballer extends extendDomainObject<Footballer>()({
  domainKey: FOOTBALLER,
  ParentDomainObject: Player,
}) {
  club: string;
}

// registry
registry.register(PLAYER, PlayerTable, Player, PlayerMapper);
registry.register(FOOTBALLER, PlayerTable, Footballer, FootballerMapper, true);

Documentation

Notation adopted (outside of Typescript code blocks):

  • a: b means that the variable name is a and its type is b.
    • a: b, c means that the variable type is b or c
  • a means that the variable name is a and its type is a Javascript object.
  • [a] means, in the context of a key of a Javascript object, that it is a key with any name.

Column options

| Column Type | Description | Options | | ----------- | ---------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | varchar | A varying length string possibly with length restrictions. | limit maximum number of characters allowed in a string | | text | A varying length string without length restrictions. | None | | int | Integer. | primaryKey: boolean whether column serves as primary key of tableforeignKey: boolean whether column is a foreign key to another tablereferences foreign key options variant: "small", "regular", "big" Size of int stored in PostgreSQL. | | serial | Integer with unique index on column. | all options of int autoGenerateExclusively whether database allows you to manually set ids for example when inserting rows into a table | | uuid | UUID. | version: "v1", "v2", "v3", "v4" Version of uuid. | | numeric | Precise floating point type. | None | | bool | Boolean. | None | | timestamp | Timestamp/Timestamptz. | timezone: boolean true for timestamptz, false for timestamp without timezone |

Create Table options

Format: createTable(options)

Arguments:

options A Javascript object taking the following keys

  • tableName: string Snakecased or camelcased name of db table
  • columns A Javascript object with keys of the form [columnName]: columnOptions

Create Data Mapper options

Format: createMapper(options)

Arguments:

options A Javascript object taking the following keys

  • domainKey: string Domain Key associated with the data mapper
  • Table: typeof Table Table associated with the data mapper
  • customColumnMap A Javascript object of the form
    {
      [tableColumnKey]: {
        fieldConversionFunction: obj => value;
      }
    }
  • customObjectMap A Javascript object of the form
    {
      domainObjectFields: {
        [domainFieldName]: {
          [domainSubfieldName]: {
            tableColumns: [col1, col2],
            columnConversionFunction: ([col1, col2]) => value;
        }
      }
    }
    Currently, customObjectMap only supports 1 level of nesting.
  • belongsTo A Javascript object of the form
    {
      [tableColumnKey]: {
        foreignKey: string;
        otherDomainKey: string;
      }
    }
  • hasOne See belongsTo.
  • hasMany See belongsTo.
  • customInheritanceOptions Currently, there is only the option to choose Single Table Inheritance.

Create Domain Object options

Format: createDomainObject<T>(options)

  • T: Classname of domain object.

Arguments:

options A Javascript object taking the following keys

  • domainKey: string Domain key associated with the domain object.

Extend Domain Object options

Format: extendDomainObject<T>()(options)

  • T: Classname of domain object

Note: The double parentheses here is not a typo. Function currying is needed here due to Typescript limitations.

Arguments:

options A Javascript object taking the following keys

  • domainKey: string Domain key associated with the domain object.
  • ParentDomainObject: typeof DomainObject The DomainObject class that the current domain object inherits from.