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

@ghuts/liteorm

v1.2.0

Published

Zero-dependency native SQLite ORM for Node.js with query builder, migrations, relations, schema builder, FTS5, encryption, audit logs, and CLI.

Readme

Lite ORM

CI npm version npm downloads Node.js >=18 npm provenance License: MIT

Zero-dependency native SQLite ORM for Node.js.

Lite ORM is a lightweight SQLite-first ORM built around a C++ N-API addon and a small JavaScript ORM layer. It gives you a query builder, schema builder, migrations, typed models, relationships, validation, transactions, caching, hooks, soft deletes, JSON fields, FTS5, encryption casts, audit logs, CLI utilities, and TypeScript declarations without adding runtime npm dependencies.

Current version: 1.2.0

Package name: @ghuts/liteorm

Repository: https://github.com/yeweroooo/lite-orm

NPM: https://www.npmjs.com/package/@ghuts/liteorm

Highlights

  • Native SQLite bridge using C++ N-API.
  • Zero runtime npm dependencies.
  • SQLite-first design: WAL, foreign keys, JSON1, FTS5, PRAGMA tuning, backup via VACUUM INTO.
  • Native prepared statements, statement cache stats, JavaScript UDFs, and custom collations.
  • Query Builder: filters, grouped conditions, joins, ordering, grouping, pagination, JSON operators, update/delete/restore.
  • Schema Builder: create/drop/rename tables, columns, indexes, unique indexes, JSON expression indexes.
  • Models: field definitions, structured validators, defaults, JSON casts, encrypted casts with key rotation, hidden/computed fields.
  • Relationships: hasOne, hasMany, belongsTo, belongsToMany.
  • Migrations: run once, status, preview, seed, rollback, generate model/migration scaffolds.
  • TypeScript-first declarations with generic model/create/update helpers.
  • Transactions with nested savepoints, retries, caching, abortable lifecycle hooks, paranoid soft delete, restore, dirty tracking.
  • CDC/sync helpers, query profiling, RBAC policies, plugin hooks.
  • FTS5 search helper with rank, highlight, snippets, sync triggers.
  • Introspection and explain query plan.
  • Audit log helper.
  • Factory, deterministic seeder, export/import JSON/CSV, repository helper.
  • CLI: init, inspect, model/migration generator, migrate/status/seed/rollback/preview, SQL studio exec, JSON export, doctor.

Requirements

  • Node.js >= 18
  • npm
  • C++ compiler
  • Python available to node-gyp
  • node-gyp available through npm lifecycle
  • SQLite is vendored in deps/sqlite/ for portable builds

On Debian/Ubuntu-like systems:

sudo apt-get install -y build-essential python3

Installation

From npm after publish:

npm install @ghuts/liteorm

From local tarball:

npm install ./ghuts-liteorm-1.2.0.tgz

From local project folder:

cd lite-orm
npm install
npm run build
npm test

The package runs node-gyp rebuild during npm install.

Quick start

const { Database, defineModel, field } = require('@ghuts/liteorm');

const db = new Database('app.sqlite');

db.schema.createTable('users', t => {
  t.increments('id');
  t.text('name').notNull();
  t.text('email').notNull().unique();
  t.text('role').default('user');
  t.json('meta');
  t.timestamps();
  t.softDeletes();
});

const User = defineModel(db, 'users', {
  fields: {
    id: field.integer().primary().autoIncrement(),
    name: field.text().required().min(2),
    email: field.text().required().email(),
    role: field.text().default('user'),
    password: field.text().hidden(),
    meta: field.json().default({})
  },
  softDelete: true,
  timestamps: true,
  json: ['meta'],
  hidden: ['password'],
  scopes: {
    admins: q => q.where('role', '=', 'admin')
  },
  computed: {
    label: user => `${user.name}<${user.email}>`
  }
});

User.create({
  name: 'Adit',
  email: '[email protected]',
  role: 'admin',
  password: 'hidden-value',
  meta: { tier: 'pro' }
});

const rows = User.query()
  .where('role', '=', 'admin')
  .whereJson('meta.tier', '=', 'pro')
  .orderBy('id', 'desc')
  .get();

console.log(rows.map(row => row.toJSON()));

Imports

CommonJS:

const {
  Database,
  defineModel,
  Model,
  QueryBuilder,
  field,
  sql,
  errors,
  sqliteVersion
} = require('@ghuts/liteorm');

TypeScript:

import { Database, defineModel, field, errors } from '@ghuts/liteorm';

Database API

Create/open database

const db = new Database('app.sqlite');
const memory = new Database(':memory:');

With options:

const db = new Database('app.sqlite', {
  cache: { ttl: 1000, max: 500 },
  busyTimeout: 5000,
  retry: { attempts: 3, delay: 10 },
  encryptionKey: Buffer.from('01234567890123456789012345678901')
});

Raw query and exec

db.exec('CREATE TABLE users(id INTEGER PRIMARY KEY, email TEXT)');
db.exec('INSERT INTO users(email) VALUES(?)', ['[email protected]']);

const rows = db.query('SELECT * FROM users WHERE email = ?', ['[email protected]']);
console.log(rows);

SQL tagged template

const { sql } = require('@ghuts/liteorm');

const rows = db.query(sql`SELECT * FROM users WHERE email = ${'[email protected]'}`);

The tag returns { text, params }.

Transactions

db.transaction(tx => {
  tx.exec('INSERT INTO users(email) VALUES(?)', ['[email protected]']);
  tx.exec('INSERT INTO users(email) VALUES(?)', ['[email protected]']);
});

If the callback throws, the transaction rolls back.

Async wrapper

const rows = await db.async.query('SELECT * FROM users');
await db.async.exec('INSERT INTO users(email) VALUES(?)', ['[email protected]']);

Current async API is a Promise-compatible wrapper around the sync native calls, so the API is ready for a future worker-thread backend without breaking user code.

Prepared statement facade

const stmt = db.prepare('SELECT * FROM users WHERE email = ?');
const one = stmt.get(['[email protected]']);
const all = stmt.all(['[email protected]']);
stmt.finalize();

stmt.run(params) is available for write statements.

PRAGMA tuning

db.tune({
  journalMode: 'WAL',
  synchronous: 'NORMAL',
  busyTimeout: 5000,
  cacheSize: -64000,
  tempStore: 'MEMORY',
  mmapSize: 268435456
});

Supported option names:

  • journalMode -> PRAGMA journal_mode
  • synchronous -> PRAGMA synchronous
  • busyTimeout -> PRAGMA busy_timeout
  • cacheSize -> PRAGMA cache_size
  • tempStore -> PRAGMA temp_store
  • mmapSize -> PRAGMA mmap_size

Backup and restore

db.backup('backup.sqlite');
db.restore('backup.sqlite');

Backup uses SQLite VACUUM INTO.

Explain query plan

const plan = db.explain(User.query().where('email', '=', '[email protected]'));
console.log(plan);

Plugins

db.use((database, opts) => {
  database.hello = () => `hello ${opts.name}`;
}, { name: 'orm' });

console.log(db.hello());

Schema Builder

Create table

db.schema.createTable('users', t => {
  t.increments('id');
  t.text('name').notNull();
  t.text('email').notNull().unique();
  t.integer('age').default(0);
  t.real('score').default(0);
  t.boolean('active').default(1);
  t.json('meta');
  t.timestamps();
  t.softDeletes();
  t.index(['email']);
  t.unique(['email']);
});

Column helpers

  • t.increments(name)
  • t.integer(name)
  • t.text(name)
  • t.real(name)
  • t.boolean(name)
  • t.json(name)
  • t.timestamps() creates created_at and updated_at
  • t.softDeletes() creates deleted_at

Column modifiers

  • .primary()
  • .autoIncrement()
  • .required() / .notNull()
  • .nullable()
  • .unique()
  • .default(value)
  • .references(table, column = 'id')

Example:

db.schema.createTable('posts', t => {
  t.increments('id');
  t.integer('user_id').notNull().references('users', 'id');
  t.text('title').notNull();
  t.text('body');
  t.timestamps();
});

Schema maintenance

db.schema.dropTable('old_table');
db.schema.renameTable('old_name', 'new_name');
db.schema.addColumn('users', 'phone', field.text().nullable());
db.schema.table('users', t => {
  t.index(['email']);
});

Schema diff and generated migration

const statements = db.schema.diff('users', {
  phone: field.text().nullable(),
  nickname: field.text().nullable()
});

const file = db.schema.generateMigration('add_user_contact_fields', statements, './migrations');
console.log(file);

diff() currently detects missing columns and produces ALTER TABLE ... ADD COLUMN ... statements.

Field definitions

Field definitions describe model behavior and validation.

const User = defineModel(db, 'users', {
  fields: {
    id: field.integer().primary().autoIncrement(),
    name: field.text().required().min(2).max(80),
    email: field.text().required().email().unique(),
    role: field.text().enum(['user', 'admin']).default('user'),
    token: field.text().encrypted(),
    meta: field.json().default({}),
    password: field.text().hidden(),
    version: field.integer().default(0)
  }
});

Available field types:

  • field.integer()
  • field.text()
  • field.real()
  • field.boolean()
  • field.json()
  • field.blob()

Available field validators/modifiers:

  • .required()
  • .notNull()
  • .nullable()
  • .unique()
  • .default(value)
  • .hidden()
  • .references(table, column)
  • .min(n)
  • .max(n)
  • .email()
  • .regex(regexp)
  • .enum(values)
  • .encrypted()

Models

Define a model

const User = defineModel(db, 'users', {
  fields: {
    name: field.text().required(),
    email: field.text().required().email()
  },
  timestamps: true,
  softDelete: true,
  json: ['meta'],
  hidden: ['password'],
  computed: {
    displayName: user => `${user.name} <${user.email}>`
  },
  validate: {
    email: v => String(v).includes('@') || 'email invalid'
  },
  scopes: {
    active: q => q.where('active', '=', 1)
  }
});

Create/find/update/delete

const user = User.create({ name: 'Adit', email: '[email protected]' });
const same = User.find(user.id);

User.query()
  .where('id', '=', user.id)
  .update({ name: 'Adit Baru' });

User.delete(user.id);        // soft delete if enabled
User.restore(user.id);       // restore soft-deleted row
User.delete(user.id, true);  // force delete

Bulk insert

const users = User.insertMany([
  { name: 'A', email: '[email protected]' },
  { name: 'B', email: '[email protected]' }
]);

Upsert

User.upsert(
  { email: '[email protected]', name: 'A Updated' },
  ['email'],
  ['name']
);

Query-builder form:

User.query()
  .insert({ email: '[email protected]', name: 'A' })
  .onConflict('email')
  .merge(['name']);

User.query()
  .insert({ email: '[email protected]', name: 'A' })
  .onConflict('email')
  .ignore();

Model instances and dirty tracking

Rows returned from models include non-enumerable helpers.

const user = User.find(1);

user.name = 'Adit Baru';
console.log(user.getChanges());
// { name: 'Adit Baru' }

user.save();
user.reload();
user.delete();
user.restore();

console.log(user.toJSON());

Optimistic locking

Use a version column and enable optimisticLock.

const User = defineModel(db, 'users', {
  fields: {
    name: field.text(),
    version: field.integer().default(0)
  },
  optimisticLock: true
});

const user = User.find(1);
user.name = 'New Name';
user.save();

If another writer changes the version before save(), errors.ConflictError is thrown.

Hidden and computed fields

const User = defineModel(db, 'users', {
  hidden: ['password', 'token'],
  computed: {
    label: u => `${u.name}<${u.email}>`
  }
});

const user = User.find(1);
console.log(user.toJSON());

Hidden fields are excluded from toJSON().

Query Builder

Basic select

const rows = User.query()
  .select(['users.id', 'users.email'])
  .where('users.email', 'LIKE', '%@test.local')
  .orderBy('users.id', 'desc')
  .limit(20)
  .offset(0)
  .get();

First row

const user = User.query()
  .where('email', '=', '[email protected]')
  .first();

Count

const total = User.query().where('role', '=', 'admin').count();

Grouped conditions

const rows = User.query()
  .where(q => q
    .where('role', '=', 'admin')
    .orWhere('email', 'LIKE', '%@company.local')
  )
  .whereBetween('age', 18, 60)
  .get();

Supported filters

  • where(column, op, value)
  • orWhere(column, op, value)
  • where(q => ...)
  • orWhere(q => ...)
  • whereNot(column, op, value)
  • whereIn(column, values)
  • whereNull(column)
  • whereBetween(column, a, b)
  • whereExists(subquery, params)
  • whereJson(path, op, value)

JSON query

User.query()
  .whereJson('meta.tier', '=', 'pro')
  .get();

With table-qualified column:

User.query()
  .whereJson('users.meta.tier', '=', 'pro')
  .get();

Joins

const rows = User.query()
  .select(['users.id', 'users.name', 'profiles.avatar'])
  .leftJoin('profiles', 'profiles.user_id', '=', 'users.id')
  .where('users.id', '>', 0)
  .get();

Group and having

const rows = User.query()
  .select(['role', 'COUNT(*) AS total'])
  .groupBy('role')
  .having('COUNT(*) > ?', [1])
  .get();

Distinct

const roles = User.query()
  .distinct()
  .select(['role'])
  .get();

Update/delete from query

User.query()
  .where('role', '=', 'guest')
  .update({ role: 'user' });

User.query()
  .where('deleted_at', 'IS NOT', null)
  .delete(true);

Soft delete helpers

User.query().get();              // excludes soft-deleted rows
User.query().withDeleted().get();
User.query().onlyDeleted().get();

Scopes

const User = defineModel(db, 'users', {
  scopes: {
    admins: q => q.where('role', '=', 'admin'),
    adults: q => q.where('age', '>=', 18)
  }
});

const rows = User.query()
  .scope('admins')
  .scope('adults')
  .get();

Cursor pagination

const page = User.query()
  .orderBy('id', 'asc')
  .cursorPaginate({ limit: 20, column: 'id' });

console.log(page.data);
console.log(page.nextCursor);
console.log(page.hasMore);

const next = User.query()
  .orderBy('id', 'asc')
  .cursorPaginate({ after: page.nextCursor, limit: 20, column: 'id' });

Query cache

const rows = User.query()
  .where('role', '=', 'admin')
  .cache(5000)
  .get();

db.clearCache();

Relationships

Register related models with the same Database instance.

hasMany

const User = defineModel(db, 'users', {
  relations: {
    posts: { type: 'hasMany', model: 'posts', foreignKey: 'user_id' }
  }
});

const Post = defineModel(db, 'posts', {
  relations: {
    user: { type: 'belongsTo', model: 'users', foreignKey: 'user_id' }
  }
});

const users = User.query().with('posts').get();

hasOne

const User = defineModel(db, 'users', {
  relations: {
    profile: { type: 'hasOne', model: 'profiles', foreignKey: 'user_id' }
  }
});

const users = User.query().with('profile').get();

belongsTo

const Post = defineModel(db, 'posts', {
  relations: {
    user: { type: 'belongsTo', model: 'users', foreignKey: 'user_id' }
  }
});

const post = Post.query().with('user').first();

belongsToMany

db.schema.createTable('role_user', t => {
  t.integer('user_id').notNull().references('users', 'id');
  t.integer('role_id').notNull().references('roles', 'id');
  t.unique(['user_id', 'role_id']);
});

const User = defineModel(db, 'users', {
  relations: {
    roles: {
      type: 'belongsToMany',
      model: 'roles',
      pivot: 'role_user',
      foreignPivotKey: 'user_id',
      relatedPivotKey: 'role_id'
    }
  }
});

const Role = defineModel(db, 'roles', {
  relations: {
    users: {
      type: 'belongsToMany',
      model: 'users',
      pivot: 'role_user',
      foreignPivotKey: 'role_id',
      relatedPivotKey: 'user_id'
    }
  }
});

const user = User.query().with('roles').first();

Constrained eager loading

const user = User.query()
  .with('posts', q => q.where('published', '=', 1).limit(5))
  .first();

Relation counts

const users = User.query()
  .withCount('posts')
  .get();

console.log(users[0].posts_count);

Migrations

Inline migrations

const migrations = [
  {
    id: '001_create_users',
    up(db) {
      db.schema.createTable('users', t => {
        t.increments('id');
        t.text('email').notNull().unique();
      });
    },
    down(db) {
      db.schema.dropTable('users');
    }
  }
];

db.migrate(migrations);
db.rollbackMigrations(1);

Migrations are tracked in _migrations and only run once.

Generate migration file

const diff = db.schema.diff('users', {
  phone: field.text().nullable()
});

const migrationPath = db.schema.generateMigration('add_phone_to_users', diff, './migrations');
console.log(migrationPath);

Validation

Field validation

const User = defineModel(db, 'users', {
  fields: {
    email: field.text().required().email(),
    username: field.text().required().min(3).max(20).regex(/^[a-z0-9_]+$/),
    role: field.text().enum(['user', 'admin']).default('user')
  }
});

Custom validation

const User = defineModel(db, 'users', {
  validate: {
    email: value => String(value).includes('@') || 'email invalid',
    age: value => Number(value) >= 18 || 'age must be >= 18'
  }
});

Validation errors throw errors.ValidationError.

try {
  User.create({ email: 'broken' });
} catch (err) {
  if (err instanceof errors.ValidationError) {
    console.error(err.message);
  }
}

Hooks

User.hook('beforeCreate', row => {
  row.email = row.email.toLowerCase();
});

User.hook('afterCreate', row => {
  console.log('created', row.id);
});

User.hook('beforeUpdate', row => {
  row.updated_by = 'system';
});

Built-in hook names used by the model layer:

  • beforeCreate
  • afterCreate
  • beforeUpdate

Custom hook names can also be registered for plugins or userland conventions.

JSON fields

Schema:

db.schema.createTable('users', t => {
  t.increments('id');
  t.json('meta');
});

Model:

const User = defineModel(db, 'users', {
  fields: {
    meta: field.json().default({})
  },
  json: ['meta']
});

Usage:

User.create({ meta: { tier: 'pro', flags: ['a', 'b'] } });

const rows = User.query()
  .whereJson('meta.tier', '=', 'pro')
  .get();

console.log(rows[0].meta.tier);

Encrypted fields

Encrypted fields use Node built-in crypto with AES-256-GCM.

const db = new Database('app.sqlite', {
  encryptionKey: Buffer.from('01234567890123456789012345678901')
});

const User = defineModel(db, 'users', {
  fields: {
    token: field.text().encrypted()
  }
});

const user = User.create({ token: 'secret-token' });
console.log(User.find(user.id).token); // secret-token

The raw SQLite value is stored with an enc: prefix and decrypted during hydration.

Audit logs

Enable audit logging:

db.audit.enable({
  actor: () => currentUserId
});

This creates audit_logs by default:

CREATE TABLE audit_logs (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  table_name TEXT,
  row_id TEXT,
  action TEXT,
  old_values TEXT,
  new_values TEXT,
  actor_id TEXT,
  created_at TEXT DEFAULT CURRENT_TIMESTAMP
)

Tracked actions:

  • create
  • upsert
  • delete
  • forceDelete
  • restore

Custom table name:

db.audit.enable({
  table: 'my_audit_logs',
  actor: () => 'system'
});

Full Text Search / FTS5

db.fts.create('posts_search', {
  columns: ['title', 'body']
});

db.fts.insert('posts_search', {
  title: 'SQLite ORM Native',
  body: 'fast local first database'
});

const results = db.fts.search('posts_search', 'native');
console.log(results);

Introspection

console.log(db.inspect.tables());
console.log(db.inspect.columns('users'));
console.log(db.inspect.indexes('users'));
console.log(db.inspect.foreignKeys('posts'));

Seeder and factory

Factory

db.factory(User, i => ({
  name: `User ${i}`,
  email: `user${i}@test.local`
})).createMany(100);

Seeder

db.seed([
  () => User.insertMany([
    { name: 'A', email: '[email protected]' },
    { name: 'B', email: '[email protected]' }
  ]),
  () => Post.create({ user_id: 1, title: 'Hello' })
]);

Export and import

JSON

db.export.json('users', 'users.json');
db.import.json('users_copy', 'users.json');

CSV

db.export.csv('users', 'users.csv');

CSV import is not currently implemented; JSON import is implemented.

Repository helper

class UserRepo {
  constructor(model, db) {
    this.model = model;
    this.db = db;
  }

  findByEmail(email) {
    return this.model.query().where('email', '=', email).first();
  }
}

const users = db.repo(User, UserRepo);
console.log(users.findByEmail('[email protected]'));

Error classes

const { errors } = require('@ghuts/liteorm');

Available classes:

  • errors.ORMError
  • errors.ValidationError
  • errors.QueryError
  • errors.MigrationError
  • errors.ConflictError
  • errors.NotFoundError
  • errors.SQLiteBusyError

Example:

try {
  User.create({ email: 'broken' });
} catch (err) {
  if (err instanceof errors.ValidationError) {
    console.error('validation failed', err.message);
  }
}

CLI

After install:

lite-orm <command>

From project source:

node bin/lite-orm.js <command>

init

lite-orm init .

Creates:

  • migrations/
  • orm.config.js

inspect

lite-orm inspect app.sqlite

Prints tables and columns.

make:migration

lite-orm make:migration create_users_table --dir migrations

Creates a migration file template.

studio non-interactive SQL

lite-orm studio app.sqlite --exec "SELECT COUNT(*) AS n FROM users"

Prints JSON rows.

export JSON

lite-orm export:json app.sqlite users users.json

Exports the table to JSON.

doctor

lite-orm doctor

Checks the Node version, package metadata, platform/architecture support, native addon loading, SQLite version, JSON1, FTS5, and temp directory write access.

Runnable examples

The npm package includes runnable examples. From a source checkout after building:

npm run build
node examples/todo/index.js
node examples/blog/index.js
node examples/auth/index.js

Run all examples:

npm run test:examples

Use a persistent database file by setting LITEORM_DB:

LITEORM_DB=./todo.sqlite node examples/todo/index.js

See also:

  • docs/examples.md
  • docs/v1.1.md
  • docs/package-readiness.md

TypeScript example

import { Database, defineModel, field } from '@ghuts/liteorm';

type UserRow = {
  id: number;
  name: string;
  email: string;
  role: string;
  version: number;
};

const db = new Database('app.sqlite');

const User = defineModel<UserRow>(db, 'users', {
  fields: {
    name: field.text().required(),
    email: field.text().required().email(),
    role: field.text().default('user'),
    version: field.integer().default(0)
  },
  optimisticLock: true
});

const user = User.create({
  name: 'Adit',
  email: '[email protected]'
});

const found = User.query()
  .where('email', '=', '[email protected]')
  .first();

Complete blog example

const { Database, defineModel, field } = require('@ghuts/liteorm');

const db = new Database('blog.sqlite');

db.tune({ journalMode: 'WAL', synchronous: 'NORMAL', busyTimeout: 5000 });

db.schema.createTable('users', t => {
  t.increments('id');
  t.text('name').notNull();
  t.text('email').notNull().unique();
  t.text('password').notNull();
  t.integer('version').default(0);
  t.timestamps();
  t.softDeletes();
});

db.schema.createTable('posts', t => {
  t.increments('id');
  t.integer('user_id').notNull().references('users', 'id');
  t.text('title').notNull();
  t.text('body');
  t.json('meta');
  t.timestamps();
  t.softDeletes();
});

const User = defineModel(db, 'users', {
  fields: {
    name: field.text().required().min(2),
    email: field.text().required().email(),
    password: field.text().hidden(),
    version: field.integer().default(0)
  },
  timestamps: true,
  softDelete: true,
  optimisticLock: true,
  hidden: ['password'],
  relations: {
    posts: { type: 'hasMany', model: 'posts', foreignKey: 'user_id' }
  }
});

const Post = defineModel(db, 'posts', {
  fields: {
    user_id: field.integer().required(),
    title: field.text().required(),
    body: field.text(),
    meta: field.json().default({})
  },
  timestamps: true,
  softDelete: true,
  json: ['meta'],
  relations: {
    user: { type: 'belongsTo', model: 'users', foreignKey: 'user_id' }
  }
});

const user = User.create({
  name: 'Adit',
  email: '[email protected]',
  password: 'secret'
});

Post.insertMany([
  { user_id: user.id, title: 'Hello SQLite', body: 'Native ORM', meta: { tags: ['sqlite'] } },
  { user_id: user.id, title: 'FTS5 Search', body: 'Fast local search', meta: { tags: ['search'] } }
]);

const loaded = User.query()
  .with('posts')
  .withCount('posts')
  .where('id', '=', user.id)
  .first();

console.log(loaded.toJSON());
console.log(loaded.posts_count);
console.log(loaded.posts.map(p => p.title));

Package structure

lite-orm/
  bin/
    lite-orm.js       CLI
  lib/
    index.js                 ORM JavaScript layer
  src/
    addon.cc                 Native C++ SQLite addon
  types/
    index.d.ts               TypeScript declarations
  examples/                  runnable examples
  docs/                      release and package docs
  deps/sqlite/               vendored SQLite amalgamation
  binding.gyp                node-gyp build config
  README.md
  LICENSE
  package.json

Development

git clone <repo>
cd lite-orm
npm install
npm run build
npm test

Run tests:

npm test

Build native addon:

npm run build

Create package tarball:

npm pack

Publish

The package is prepared for npm publish as v1.2.0.

Pre-publish validation:

npm run build
npm test
npm pack --dry-run
npm publish --dry-run --access public

Publish:

npm publish --access public

Verify registry:

npm view @ghuts/liteorm version

Expected output:

1.2.0

Smoke test from tarball

cd lite-orm
PACK_DIR="$(mktemp -d)"
SMOKE_DIR="$(mktemp -d)"
npm pack --pack-destination "$PACK_DIR"
PKG_PATH="$(node -e "const fs=require('fs'),path=require('path'); const d=process.argv[1]; const f=fs.readdirSync(d).find(x=>x.endsWith('.tgz')); if(!f) throw new Error('tarball not found'); console.log(path.join(d,f));" "$PACK_DIR")"

cd "$SMOKE_DIR"
npm init -y
npm install "$PKG_PATH"

node - <<'NODE'
const { Database, defineModel, field } = require('@ghuts/liteorm');
const db = new Database(':memory:');
db.schema.createTable('users', t => {
  t.increments('id');
  t.text('email').notNull().unique();
  t.text('name');
});
const User = defineModel(db, 'users', {
  fields: {
    email: field.text().email().required(),
    name: field.text().min(2)
  }
});
User.create({ email: '[email protected]', name: 'Smoke' });
console.log(User.query().count());
NODE

Expected output:

1

Current verification status

Last verified in this project:

npm run build: pass
npm test: all tests pass
npm run test:types: pass
npm run test:examples: pass
npm pack --dry-run: pass
npm publish --dry-run --access public: pass
smoke install from ghuts-liteorm-1.2.0.tgz: pass

Notes and implementation details

  • Runtime npm dependencies: none.
  • Native addon builds against the vendored SQLite amalgamation in deps/sqlite/.
  • db.prepare() uses native SQLite prepared statements with explicit finalization and statement-cache support.
  • db.async.* is Promise-compatible but uses the current synchronous native backend internally.
  • JSON support uses SQLite JSON1 functions for whereJson.
  • FTS support requires SQLite compiled with FTS5.
  • Encryption uses Node built-in crypto and AES-256-GCM.

Maintainer

Maintained by Lsow: https://github.com/yeweroooo

Repository: https://github.com/yeweroooo/lite-orm

License

MIT