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

@promakeai/orm

v1.0.6

Published

Database-agnostic ORM core - works in browser and Node.js

Readme

@promakeai/orm

Core ORM package with schema DSL, query builder, and multi-language support. Platform-agnostic - works in both browser and Node.js.

Installation

npm install @promakeai/orm

Schema Definition

import { defineSchema, f } from '@promakeai/orm';

const schema = defineSchema({
  name: 'myapp',
  languages: ['en', 'tr', 'de'],
  tables: {
    users: {
      id: f.id(),
      email: f.string().required().unique().lowercase(),
      name: f.string().required().trim(),
      age: f.int().min(0).max(150),
      role: f.string().enum(['user', 'admin']).default('user'),
      bio: f.text().translatable(),
      createdAt: f.timestamp(),
      metadata: f.json(),
    },
    products: {
      id: f.id(),
      sku: f.string().required().unique(),
      price: f.decimal().required().min(0),
      stock: f.int().default(0),
      name: f.string().translatable().required(),
      description: f.text().translatable(),
      categoryId: f.int().ref('categories'),
      tagIds: f.json().ref('tags'),  // Array reference
    },
    categories: {
      id: f.id(),
      slug: f.string().required().unique(),
      name: f.string().translatable().required(),
      parentId: f.int().ref({
        table: 'categories',
        onDelete: 'SET_NULL',
      }),
    },
  },
});

JSON Schema (AI-Friendly)

The JSON schema format is the runtime-friendly version and supports native arrays and typed objects:

{
  "name": "myapp",
  "languages": ["en", "tr"],
  "defaultLanguage": "en",
  "tables": {
    "products": {
      "id": { "type": "id" },
      "tags": { "type": ["string"] },
      "metadata": { "type": { "color": "string", "weight": "number" } },
      "variants": { "type": [{ "sku": "string", "price": "number" }] }
    }
  }
}

Field Types

| Type | SQL | Description | |------|-----|-------------| | f.id() | INTEGER PRIMARY KEY AUTOINCREMENT | Auto-increment primary key | | f.string() | VARCHAR | Short text | | f.text() | TEXT | Long text | | f.int() | INTEGER | Integer | | f.decimal() | REAL/DECIMAL | Decimal number | | f.bool() | INTEGER (0/1) | Boolean | | f.timestamp() | TEXT (ISO) | ISO datetime string | | f.json() | TEXT | JSON serialized data |

JSON schema type syntax supports string[], number[], boolean[], object, and object[] which are stored as JSON in SQL (TEXT) and typed in TS.

Field Modifiers

Constraints

f.string()
  .required()              // NOT NULL
  .nullable()              // Allow NULL (default)
  .unique()                // UNIQUE constraint
  .primary()               // PRIMARY KEY
  .default('value')        // DEFAULT value

String Transforms

f.string()
  .trim()                  // Remove whitespace
  .lowercase()             // Convert to lowercase
  .uppercase()             // Convert to uppercase

Validation

f.string()
  .minLength(1)            // Minimum length
  .maxLength(255)          // Maximum length
  .enum(['a', 'b', 'c'])   // Allowed values
  .match(/^[a-z]+$/)       // RegExp pattern

f.int()
  .min(0)                  // Minimum value
  .max(100)                // Maximum value

References

// Simple reference
f.int().ref('users')

// With options
f.int().ref({
  table: 'users',
  field: 'id',             // Default: 'id'
  onDelete: 'CASCADE',     // CASCADE | SET_NULL | RESTRICT | NO_ACTION
  onUpdate: 'CASCADE',
})

// Array reference (JSON field with refs)
f.json().ref('tags')

Multi-Language

f.string().translatable()  // Stored in {table}_translations
f.text().translatable()

Query Builder (MongoDB-style)

import { buildWhereClause } from '@promakeai/orm';

// Simple equality
buildWhereClause({ status: 'active' });
// WHERE status = ?  params: ['active']

// Comparison operators
buildWhereClause({ price: { $gt: 100 } });    // > 100
buildWhereClause({ price: { $gte: 100 } });   // >= 100
buildWhereClause({ price: { $lt: 100 } });    // < 100
buildWhereClause({ price: { $lte: 100 } });   // <= 100
buildWhereClause({ status: { $ne: 'deleted' } }); // != 'deleted'

// Array operators
buildWhereClause({ id: { $in: [1, 2, 3] } });    // IN (?, ?, ?)
buildWhereClause({ id: { $nin: [1, 2, 3] } });   // NOT IN (?, ?, ?)

// String matching
buildWhereClause({ name: { $like: '%john%' } });     // LIKE ?
buildWhereClause({ name: { $notLike: '%test%' } });  // NOT LIKE ?

// Range and null
buildWhereClause({ price: { $between: [10, 100] } }); // BETWEEN ? AND ?
buildWhereClause({ deletedAt: { $isNull: true } });   // IS NULL
buildWhereClause({ email: { $isNull: false } });      // IS NOT NULL

// JSON array contains
buildWhereClause({ tags: { $contains: "sale" } });    // json_each(...) = "sale"
buildWhereClause({ tags: { $containsAny: ["sale", "new"] } }); // json_each(...) IN (...)

// Logical operators
buildWhereClause({
  $or: [
    { active: true },
    { role: 'admin' }
  ]
});
// (active = ? OR role = ?)

buildWhereClause({
  $and: [
    { price: { $gt: 50 } },
    { stock: { $gt: 0 } }
  ]
});
// (price > ? AND stock > ?)

buildWhereClause({
  $nor: [
    { banned: true },
    { suspended: true }
  ]
});
// NOT (banned = ? OR suspended = ?)

// Negation
buildWhereClause({
  age: { $not: { $lt: 18 } }
});
// NOT (age < ?)

// Combined
buildWhereClause({
  status: 'active',
  price: { $gt: 100, $lt: 500 },
  category: { $in: ['electronics', 'books'] },
});
// status = ? AND price > ? AND price < ? AND category IN (?, ?)

Multi-Language Support

Schema Configuration

const schema = defineSchema({
  languages: ['en', 'tr', 'de'],  // Supported languages
  // OR
  languages: {
    default: 'en',
    supported: ['en', 'tr', 'de'],
  },
  tables: {
    products: {
      id: f.id(),
      price: f.decimal(),
      name: f.string().translatable(),      // In translation table
      description: f.text().translatable(), // In translation table
    },
  },
});

Generated Tables

-- Main table
CREATE TABLE products (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  price REAL
);

-- Translation table
CREATE TABLE products_translations (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  product_id INTEGER NOT NULL,
  lang TEXT NOT NULL,
  name TEXT,
  description TEXT,
  FOREIGN KEY (product_id) REFERENCES products(id) ON DELETE CASCADE,
  UNIQUE (product_id, lang)
);

Translation Query Builder

import { buildTranslationQuery } from '@promakeai/orm';

const { sql, params } = buildTranslationQuery('products', schema, {
  lang: 'tr',
  fallbackLang: 'en',
  where: { price: { $gt: 100 } },
  orderBy: [{ field: 'name', direction: 'ASC' }],
  limit: 10,
});

// SELECT
//   m.id, m.price,
//   COALESCE(t.name, fb.name) AS name,
//   COALESCE(t.description, fb.description) AS description
// FROM products m
// LEFT JOIN products_translations t ON m.id = t.product_id AND t.lang = ?
// LEFT JOIN products_translations fb ON m.id = fb.product_id AND fb.lang = ?
// WHERE price > ?
// ORDER BY name ASC
// LIMIT 10

Populate Resolver

Batch-fetches references to prevent N+1 queries:

Populate options accept a space-separated string, string array, or nested object for deeper population.

import { resolvePopulate } from '@promakeai/orm';

const posts = await adapter.list('posts');

// Resolve author references
const postsWithAuthors = await resolvePopulate(
  posts,
  { userId: true },
  schema,
  adapter
);
// [{ id: 1, title: '...', userId: { id: 1, name: 'John' } }]

// Nested populate
const postsWithDetails = await resolvePopulate(
  posts,
  {
    userId: true,
    comments: {
      populate: { authorId: true },
      where: { approved: true },
      limit: 5,
    },
  },
  schema,
  adapter
);

// Array reference populate
const postsWithTags = await resolvePopulate(
  posts,
  { tagIds: true },  // JSON array of tag IDs
  schema,
  adapter
);

Schema Helpers

import {
  getTranslatableFields,
  getNonTranslatableFields,
  getReferenceFields,
  getPrimaryKeyField,
  getRequiredFields,
  isRequiredField,
  getMainTableFields,
  getTranslationTableFields,
  toTranslationTableName,
  toTranslationFKName,
  singularize,
  pluralize,
  toPascalCase,
  toCamelCase,
  toSnakeCase,
} from '@promakeai/orm';

const table = schema.tables.products;

getTranslatableFields(table);    // ['name', 'description']
getNonTranslatableFields(table); // ['id', 'price', 'sku', 'stock', 'categoryId']
getReferenceFields(table);       // [['categoryId', { table: 'categories', ... }]]
getPrimaryKeyField(table);       // 'id'
getRequiredFields(table);        // ['sku', 'price', 'name']

toTranslationTableName('products');  // 'products_translations'
toTranslationFKName('products');     // 'product_id'

singularize('products');  // 'product'
pluralize('product');     // 'products'
toPascalCase('user_id');  // 'UserId'
toCamelCase('user_id');   // 'userId'
toSnakeCase('userId');    // 'user_id'

Schema Validation

import {
  validateSchema,
  assertValidSchema,
  isValidSchema,
} from '@promakeai/orm';

// Returns array of errors
const errors = validateSchema(schema);
// [{ path: 'tables.users.email', message: '...' }]

// Throws on invalid schema
assertValidSchema(schema);

// Boolean check
if (isValidSchema(schema)) {
  // ...
}

Schema Merging

import { mergeSchemas } from '@promakeai/orm';

const schema1 = defineSchema({
  tables: { users: { ... } },
});

const schema2 = defineSchema({
  tables: { products: { ... } },
});

const merged = mergeSchemas([schema1, schema2]);
// { tables: { users: {...}, products: {...} } }

IDataAdapter Interface

All adapters must implement this interface:

interface IDataAdapter {
  schema?: SchemaDefinition;
  defaultLang?: string;

  // Query methods
  list<T>(table: string, options?: QueryOptions): Promise<T[]>;
  get<T>(table: string, id: string | number, options?: QueryOptions): Promise<T | null>;
  count(table: string, options?: QueryOptions): Promise<number>;
  paginate<T>(table: string, page: number, limit: number, options?: QueryOptions): Promise<PaginatedResult<T>>;

  // Write methods
  create<T>(table: string, data: Record<string, unknown>): Promise<T>;
  update<T>(table: string, id: string | number, data: Record<string, unknown>): Promise<T>;
  delete(table: string, id: string | number): Promise<boolean>;

  // Batch methods
  createMany<T>(table: string, records: Record<string, unknown>[], options?: { ignore?: boolean }): Promise<{ created: number; ids: (number | bigint)[] }>;
  updateMany(table: string, updates: { id: number | string; data: Record<string, unknown> }[]): Promise<{ updated: number }>;
  deleteMany(table: string, ids: (number | string)[]): Promise<{ deleted: number }>;

  // Translation methods
  createWithTranslations<T>(table: string, data: Record<string, unknown>, translations?: Record<string, Record<string, unknown>>): Promise<T>;
  upsertTranslation(table: string, id: string | number, lang: string, data: Record<string, unknown>): Promise<void>;
  getTranslations<T>(table: string, id: string | number): Promise<T[]>;

  // Raw queries
  raw<T>(query: string, params?: unknown[]): Promise<T[]>;
  execute(query: string, params?: unknown[]): Promise<{ changes: number; lastInsertRowid: number | bigint }>;

  // Transactions
  beginTransaction(): Promise<void>;
  commit(): Promise<void>;
  rollback(): Promise<void>;

  // Schema
  getTables?(): Promise<string[]>;
  getTableSchema?(table: string): Promise<unknown[]>;

  // Lifecycle
  connect?(): void | Promise<void>;
  close(): void | Promise<void>;
}

TypeScript Types

import type {
  SchemaDefinition,
  TableDefinition,
  FieldDefinition,
  FieldType,
  FieldReference,
  QueryOptions,
  WhereClause,
  OrderByOption,
  PopulateOption,
  PaginatedResult,
  IDataAdapter,
} from '@promakeai/orm';

Related Packages

License

MIT