An open API service indexing awesome lists of open source software.

https://github.com/ichiriac/normaljs

Normal : Node Object Relational Mapper Abstraction Layer
https://github.com/ichiriac/normaljs

activerecord javascript javascript-vanilla knex mariadb mysql nodejs orm postgres postgresql sql sqlite transaction typescript

Last synced: 14 days ago
JSON representation

Normal : Node Object Relational Mapper Abstraction Layer

Awesome Lists containing this project

README

          


Normal ORM

# NormalJS

The simple, straightforward, and most advanced Node.js ORM — without the bloat.

Build data-rich apps with clean models, powerful relations, and first-class DX. NormalJS blends a tiny API with serious capabilities: schema sync, relations (1:n, n:m), transactions, model extension, and active-record-style instances. All in plain JavaScript.

## Why NormalJS

- Simple: minimal surface area. Define models with a static `fields` object and go.
- Transaction-first: fully isolated repos inside transactions without leaking state.
- Advanced caching: centralized in-memory cache shared across child processes, with UDP-based clustering for peer invalidation.
- Powerful: relations (1:n, n:m), transactions, model mixins/extension, inheritance with discriminators, relation proxies.
- Productive: active records you can call methods on; lazy, ID-first reads that auto-hydrate fields; request-level caching with invalidation markers.
- Portable: works with Postgres and SQLite. Uses Knex under the hood.

### What makes NormalJS different for complex domains

- Extensible field system: add custom field types that control serialization, JSON output, schema, and lifecycle hooks.
- Model extension and overwrite: register multiple classes with the same `static _name` to merge fields and attach static/instance behavior over time.
- Inheritance with discriminators: share a base model schema and behavior; allocate correct child records automatically.
- Schema sync (base synchronization): generate and evolve tables from model fields with migration-safe helpers.
- Clear split of responsibilities: simple static APIs for model-level operations, and instance methods/getters for active records.

## Install

[![Coverage Status](https://coveralls.io/repos/github/ichiriac/normal/badge.svg?branch=main)](https://coveralls.io/github/ichiriac/normal?branch=main)
[![license](https://img.shields.io/github/license/ichiriac/normal)](https://github.com/ichiriac/normal/blob/main/LICENSE)
[![NPM version](https://badge.fury.io/js/normaljs.svg)](http://badge.fury.io/js/normaljs)

```bash
npm install normaljs pg -y
```

### TypeScript Support

NormalJS is written in TypeScript and includes full type definitions. TypeScript users get:

- **Type-safe model definitions** with autocomplete for fields and relations
- **Type inference** for query results and active records
- **Generic types** for models and records
- **Declaration files** (`.d.ts`) for all public APIs

```bash
npm install normaljs pg
# TypeScript types are included - no @types package needed
```

```typescript
class MyModel {
static table = 'my_table';
static fields = {
/* ... */
};
}
```

## Database engines

NormalJS supports these SQL databases via Knex:

PostgreSQL
MariaDB
MySQL
CockroachDB
SQLite
Oracle
Microsoft SQL Server
Amazon Redshift

Note: You only need the driver for the database(s) you use (e.g., `pg` for PostgreSQL, `sqlite3` or `better-sqlite3` for SQLite).

## 60‑second Quickstart

### TypeScript

```typescript
// index.ts
import { Connection, Repository } from 'normaljs';

// 1) Create a connection (SQLite in-memory shown; Postgres or MySQL also supported)
const db = new Connection({ client: 'sqlite3', connection: { filename: ':memory:' } });

// 2) Define models
class Users {
static table = 'users';
static fields = {
id: 'primary' as const,
firstname: { type: 'string', required: true },
lastname: { type: 'string', required: true },
email: { type: 'string', unique: true, required: true },
created_at: { type: 'datetime', default: () => new Date() },
updated_at: { type: 'datetime', default: () => new Date() },
};

get name() {
return `${this.firstname} ${this.lastname}`;
}
}
// Override readonly 'name' property for NormalJS
Object.defineProperty(Users, 'name', { value: 'Users', configurable: true });

class Posts {
static table = 'posts';
static fields = {
id: 'primary' as const,
title: { type: 'string', required: true },
content: { type: 'string', required: true },
author_id: { type: 'many-to-one', required: true, model: 'Users' },
};
}
Object.defineProperty(Posts, 'name', { value: 'Posts', configurable: true });

// 3) Register & sync
const repo = new Repository(db);
repo.register({ Users, Posts });
await repo.sync({ force: true });

// 4) Use it
const u = await repo
.get('Users')
.create({ firstname: 'Ada', lastname: 'Lovelace', email: 'ada@example.com' });
const p = await repo.get('Posts').create({ title: 'Hello', content: 'World', author_id: u.id });
console.log(u.name); // "Ada Lovelace"
```

### JavaScript (CommonJS)

```js
// index.js
const { Connection, Repository } = require('normaljs');

// 1) Create a connection (SQLite in-memory shown; Postgres or MySQL also supported)
const db = new Connection({ client: 'sqlite3', connection: { filename: ':memory:' } });

// 2) Define models
class Users {
static table = 'users';
static fields = {
id: 'primary',
firstname: { type: 'string', required: true },
lastname: { type: 'string', required: true },
email: { type: 'string', unique: true, required: true },
created_at: { type: 'datetime', default: () => new Date() },
updated_at: { type: 'datetime', default: () => new Date() },
};

get name() {
return `${this.firstname} ${this.lastname}`;
}
}
// Override readonly 'name' property for NormalJS
Object.defineProperty(Users, 'name', { value: 'Users', configurable: true });

class Posts {
static table = 'posts';
static fields = {
id: 'primary',
title: { type: 'string', required: true },
content: { type: 'string', required: true },
author_id: { type: 'many-to-one', required: true, model: 'Users' },
};
}
Object.defineProperty(Posts, 'name', { value: 'Posts', configurable: true });

// 3) Register & sync
const repo = new Repository(db);
repo.register({ Users, Posts });
await repo.sync({ force: true });

// 4) Use it
const u = await repo
.get('Users')
.create({ firstname: 'Ada', lastname: 'Lovelace', email: 'ada@example.com' });
const p = await repo.get('Posts').create({ title: 'Hello', content: 'World', author_id: u.id });
console.log(u.name); // "Ada Lovelace"
```

### Modeling big domains, simply

Static methods live on models; instance methods live on records. You can extend models incrementally or inherit from a base model.

```js
// Extension: register the same model name again to add fields + behavior
class Users {
static _name = 'Users';
static fields = { id: 'primary' };
}

// Extend Users with fields and static/instance APIs
class UsersExt {
static _name = 'Users';
static fields = { email: 'string' };
static byEmail(email) {
return this.where({ email }).first(); // simple, model-scoped static API
}
get domain() {
return this.email?.split('@')[1] || null; // instance API on active record
}
}

// Inheritance: child model shares base structure and behavior
class Payment {
static _name = 'Payment';
static fields = { id: 'primary', amount: 'float' };
}
class CardPayment {
static _name = 'CardPayment';
static inherits = 'Payment';
static fields = { pan: 'string' };
}

repo.register(Users);
repo.register(UsersExt); // extension merged
repo.register({ Payment, CardPayment });
```

## Features at a glance

- Models
- Simple class with `static _name`, `static table`, `static fields`.
- Extension system: register multiple times with same `static _name` to add/override fields and behavior.
- Inheritance with discriminators for polymorphic models.
- **NEW**: Model scopes for reusable query filters and eager loading patterns.
- Fields
- Built-ins: primary, integer/float, string/text, boolean, date/datetime, enum, json, reference.
- Constraints: `default`, `required`, `unique`, `index`.
- Custom fields: implement serialization, JSON, schema, and lifecycle hooks.
- **NEW**: Model-level indexes for composite, unique, and partial indexes (e.g., `static indexes = { idx_name: { fields: ['email', 'company'], unique: true } }`).
- Relations
- 1:n via `one-to-many` (e.g., `comments: { type: 'one-to-many', foreign: 'Comments.post_id' }`).
- n:m via paired `many-to-many` (auto-join table).
- Relation proxies on instances: `add`, `remove`, `load`.
- **NEW**: Automatic join generation for relational filters (e.g., `where({ 'author.organization.name': 'ACME' })`).
- Scopes
- Define reusable query patterns at the model level.
- Support for `defaultScope` applied to all queries unless bypassed.
- Parameterized scopes via functions.
- Composable scopes with deterministic merging.
- **NEW**: See `docs/scopes.md` for comprehensive guide.
- Transactions
- `repo.transaction(async (tx) => { /* ... */ })` gives an isolated tx-bound repository.
- Post-commit cache flush of changed records.
- Active records
- Rows are wrapped into instances; instance methods and getters work naturally.
- Default reads select only `id` (fast), with lazy hydration from cache/DB.
- Cache and discovery
- Request-level caching via `.cache(ttl)` and entry cache per `Model:ID`.
- Per-model invalidation markers (`$Model`) to evict request caches without dropping entry caches.
- Centralized in-memory cache across processes with UDP-based clustering.
- Discovery protocol auto-syncs peer list for invalidations.
- Repository Context
- **NEW**: Repository-wide context accessible in transactions, models, and records.
- `get_context(key, defaultValue)` retrieves context values.
- `set_context(key, value)` stores context values.
- Transactions inherit parent context but modifications are isolated.
- Use cases: multi-tenancy, user context, feature flags, request metadata.
- Schema sync
- Create/update tables from model fields with `repo.sync()`.
- Migration-safe helpers for column replacement and index updates.

See full field reference in `docs/fields.md`.

### More docs

- `docs/models.md` — Model definitions, inheritance, and extension system.
- `docs/fields.md` — Built-in field types and options.
- `docs/scopes.md` — **NEW**: Model scopes for reusable query filters.
- `docs/requests.md` — Request API, criteria DSL, and request-level caching.
- `docs/relational-filters.md` — **NEW**: Automatic joins for relational field filters.
- `docs/cache.md` — Cache architecture, connection options, discovery, and model cache options.
- `docs/custom-fields.md` — In-depth custom fields with hooks and a file-storage example.
- `docs/adoption-sequelize.md` — Step-by-step migration guide from Sequelize.

## Demo

Explore `demo/` for a working blog schema (Users, Posts, Tags, Comments) and a CRM and Stocks example.

## License

The MIT License (MIT)