Skip to content
toshihikojsPublic

About

🥚 Yet another simple ORM for Node.js.

Topics

Resources

Stars

256 stars

Watchers

22 watching

Forks

Repository files navigation

Toshihiko

Toshihiko

Yet another simple ORM for Node.js.

CI Coverage Node.js 22 and 24 MIT License

Yet another simple ORM for Node.js.

Project status: Toshihiko v2 is under active development. The current packages use prerelease versions and require Node.js 22 or newer.

Why Toshihiko?

  • Schema-derived TypeScript types. Model rows, query fields, primary keys, defaults, and custom field values are inferred directly from define().
  • Promise APIs. Queries, adapters, validators, writes, and transactions use native Promises.
  • Model-centered API. Model, Query, Yukari, field types, where(), find(), and findById() form one consistent vocabulary.
  • Explicit adapter boundaries. Database-specific connections and result types stay inside adapter packages instead of leaking into the ORM core.
  • Service-backed tests. GitHub Actions covers Node.js 22 and 24, with MySQL 5.7 and 8.4, Redis, and Memcached.

Quick start

Install the core and the MySQL adapter:

npm install toshihiko @toshihiko/mysql-adapter

Define a model:

import { Toshihiko, Type } from 'toshihiko';

const database = new Toshihiko('mysql', {
  database: 'app',
  host: '127.0.0.1',
  password: 'secret',
  username: 'root',
});

const User = database.define('users', [
  {
    name: 'id',
    column: 'user_id',
    type: Type.Integer,
    primaryKey: true,
    autoIncrement: true,
  },
  {
    name: 'name',
    type: Type.String,
  },
]);

const user = User.build({
  name: 'Alice',
});

await user.insert();

const persistedUser = await User.findById(user.id);
if (persistedUser) {
  persistedUser.name = 'Updated Alice';
  await persistedUser.save();
}

const users = await User
  .where({ id: { $gte: 1 }, name: { $like: 'A%' } })
  .orderBy({ id: 'desc' })
  .limit(10)
  .find(true);

const userCount = await User.where({ name: { $like: 'A%' } }).count();

await persistedUser?.delete();

build() returns a typed Yukari instance, so Toshihiko infers id as number, name as string, and the primary key as id directly from the schema without requiring a separately maintained row interface or type alias. insert() validates the Yukari, persists it through the configured Adapter, and hydrates database-generated values back into the same instance. An inserted Yukari remains a new row; query it before updating or deleting it. update() validates queried data and writes changed fields using the original primary key. save() inserts new rows and updates queried rows, while delete() removes a queried row using its original primary key.

Packages

Toshihiko is developed as a monorepo, but each package keeps an independent public API and release boundary.

Package Coverage Directory Purpose
toshihiko toshihiko coverage packages/toshihiko ORM core, Model, Query, Yukari, and built-in field types
@toshihiko/base-adapter base adapter coverage packages/base-adapter Typed, Promise-only foundation for adapter authors; installed transitively by concrete adapters
@toshihiko/mysql-adapter MySQL adapter coverage packages/mysql-adapter MySQL adapter built on the mysql2 Promise API
@toshihiko/base-cache base cache coverage packages/base-cache Typed, Promise-only foundation for cache implementations
@toshihiko/redis-cache Redis cache coverage packages/redis Redis cache with stable key generation, batching, and positional misses
@toshihiko/memcached-cache Memcached cache coverage packages/memcached Memcached cache with batched reads and customizable keys
@toshihiko/sql-utils SQL utils coverage packages/sql-utils SQL identifier mapping and escaping utilities

The dependency direction is intentionally small:

flowchart LR
  mysql["@toshihiko/mysql-adapter"] --> base["@toshihiko/base-adapter"]
  mysql --> core["toshihiko"]
  mysql --> sql["@toshihiko/sql-utils"]
  redis["@toshihiko/redis-cache"] --> cache["@toshihiko/base-cache"]
  memcached["@toshihiko/memcached-cache"] --> cache
  cache --> core
  base --> core
  core --> sql
Loading

Type inference

Built-in and custom field types flow through the complete model API:

const Article = database.define('articles', [
  { name: 'id', type: Type.Integer, primaryKey: true },
  { name: 'title', type: Type.String },
  { name: 'publishedAt', type: Type.Datetime, allowNull: true },
  { name: 'metadata', type: Type.Json },
]);

const article = Article.build({
  id: 1,
  title: 'Typed models without duplicate interfaces',
  publishedAt: null,
  metadata: { language: 'en' },
});

article.title = 'Types inferred directly from build()';
await article.validateAll();

await Article.findById(1);

// Query fields and primary-key values use the same inferred schema.
Article.where({ title: { $like: 'Typed%' } });

Model.build() returns a Yukari instance whose known and optional properties reflect the supplied input and schema defaults. Asynchronous validators can then run through validateAll().

Documentation

The complete v2 documentation is available in three languages:

The source documentation covers the current TypeScript and Promise APIs:

Adapter model

The core depends on a small Promise-based adapter contract. Concrete adapters own their connection, query, field, and mutation result types. A dialect name loads the corresponding installed Adapter package:

const database = new Toshihiko('mysql', {
  database: 'app',
});

Adapter constructors and instances can also be injected directly. Toshihiko calls a constructor with (toshihiko, options); @toshihiko/base-adapter and @toshihiko/mysql-adapter also support standalone new Adapter(options) construction.

import { MySQLAdapter } from '@toshihiko/mysql-adapter';

const injectedDatabase = new Toshihiko(MySQLAdapter, {
  database: 'app',
});

The MySQL Adapter uses mysql2 prepared execution for bound values and implements Toshihiko query operators.

Caching

Redis and Memcached cache implementations are developed and released from this monorepo. Cache instances can be configured globally and inherited by Models, overridden for one Model, or disabled with cache: false. The MySQL cache path is regression-tested with Memcached; Redis batch reads preserve key positions with null misses.

import { MemcachedCache } from '@toshihiko/memcached-cache';

const cache = new MemcachedCache('127.0.0.1:11211', {
  prefix: 'app:',
});

const cachedDatabase = new Toshihiko('mysql', {
  cache,
  database: 'app',
});

Queries read and fill the configured cache using the original Toshihiko key flow. Updates and deletes invalidate matching primary-key entries before the database mutation. Pass { noCache: true } to find() to bypass cache reads for one query.

Development

Requirements

  • Node.js 22 or 24
  • npm
  • Rush 5.172.1

Install the development tools once:

npm install --global @microsoft/rush@5.172.1

Install all workspace dependencies through Rush's npm integration:

rush update

Rush downloads the pinned official npm version declared in rush.json, then manages installation, the project graph, build order, tests, version policies, and releases.

rush check
rush build
rush typecheck
rush test

Run a command for a specific package and its dependencies:

rush build --to @toshihiko/mysql-adapter
rush test --to @toshihiko/mysql-adapter

Testing

Unit and package-contract tests run locally without external services:

rush build
rush test

Service-backed tests run in GitHub Actions against MySQL 5.7 and MySQL 8.4 together with Redis and Memcached. Local development does not require Docker. If you already have compatible services, run the integration suite explicitly:

MYSQL_DATABASE=toshihiko_test \
MYSQL_HOST=127.0.0.1 \
MYSQL_PASSWORD=toshihiko \
MYSQL_PORT=3306 \
MYSQL_USER=root \
REDIS_HOST=127.0.0.1 \
REDIS_PORT=6379 \
MEMCACHED_HOST=127.0.0.1 \
MEMCACHED_PORT=11211 \
rush test:integration

Versioning and releases

Rush tracks changes and publishes packages independently:

  • toshihiko, @toshihiko/base-adapter, @toshihiko/mysql-adapter, @toshihiko/base-cache, @toshihiko/redis-cache, and @toshihiko/memcached-cache are locked to major version 2, but do not need to publish together.
  • @toshihiko/sql-utils remains on its independent 1.x version line.
  • A change to an adapter does not force an unrelated core release.

Contributors should add a Rush change file for user-visible package changes:

rush change

Contributing

Issues and pull requests are welcome. Before opening a pull request:

  1. Install dependencies with rush update.
  2. Run rush check, rush build, rush typecheck, and rush test.
  3. Add or update tests for behavioral changes.
  4. Add a Rush change file when the change affects a published package.

Please keep public API changes intentional and update regression tests when behavior changes.

About the name

Toshihiko is a character from Touhou Warring States Nights, a collaborative Touhou fan work. In that character setting, Toshihiko is a manifested instance of Yakumo Yukari. This is why the API calls a record object a Yukari instance: the technical object-instance meaning echoes the relationship between the two characters. The names have been part of the project since its first release in 2014.

Acknowledgements

Toshihiko exists thanks to its maintainers, contributors, and users. Special thanks to the contributors highlighted by the original project:

License

MIT

About

🥚 Yet another simple ORM for Node.js.

Topics

Resources

Stars

256 stars

Watchers

22 watching

Forks

Releases

Packages

Used by

Contributors

Languages